Просмотр исходного кода

Update tests

GitOrigin-RevId: 9272720e0f6865c54257c43bd98d8e6003251aa2
Andrew Rumble 2 лет назад
Родитель
Сommit
2bfb55a305
39 измененных файлов с 870 добавлено и 829 удалено
  1. 19 20
      services/web/test/unit/src/Analytics/AnalyticsControllerTests.mjs
  2. 16 18
      services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddlewareTests.mjs
  3. 36 25
      services/web/test/unit/src/Collaborators/CollaboratorsControllerTests.mjs
  4. 38 28
      services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.mjs
  5. 33 22
      services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.mjs
  6. 17 15
      services/web/test/unit/src/Contact/ContactControllerTests.mjs
  7. 11 12
      services/web/test/unit/src/Cooldown/CooldownMiddlewareTests.mjs
  8. 12 12
      services/web/test/unit/src/DocumentUpdater/DocumentUpdaterControllerTests.mjs
  9. 16 16
      services/web/test/unit/src/Documents/DocumentControllerTests.mjs
  10. 15 24
      services/web/test/unit/src/Downloads/ProjectDownloadsControllerTests.mjs
  11. 21 22
      services/web/test/unit/src/Downloads/ProjectZipStreamManagerTests.mjs
  12. 22 31
      services/web/test/unit/src/Exports/ExportsControllerTests.mjs
  13. 17 20
      services/web/test/unit/src/Exports/ExportsHandlerTests.mjs
  14. 13 13
      services/web/test/unit/src/FileStore/FileStoreControllerTests.mjs
  15. 30 22
      services/web/test/unit/src/LinkedFiles/LinkedFilesControllerTests.mjs
  16. 10 11
      services/web/test/unit/src/Metadata/MetaControllerTests.mjs
  17. 13 12
      services/web/test/unit/src/Metadata/MetaHandlerTests.mjs
  18. 13 13
      services/web/test/unit/src/Notifications/NotificationsControllerTests.mjs
  19. 29 30
      services/web/test/unit/src/PasswordReset/PasswordResetControllerTests.mjs
  20. 20 19
      services/web/test/unit/src/PasswordReset/PasswordResetHandlerTests.mjs
  21. 27 39
      services/web/test/unit/src/Project/DocLinesComparitorTests.mjs
  22. 7 12
      services/web/test/unit/src/Project/ProjectApiControllerTests.mjs
  23. 47 40
      services/web/test/unit/src/Project/ProjectListControllerTests.mjs
  24. 7 7
      services/web/test/unit/src/Referal/ReferalConnectTests.mjs
  25. 0 11
      services/web/test/unit/src/Referal/ReferalControllerTests.js
  26. 11 0
      services/web/test/unit/src/Referal/ReferalControllerTests.mjs
  27. 11 13
      services/web/test/unit/src/Referal/ReferalHandlerTests.mjs
  28. 46 59
      services/web/test/unit/src/References/ReferencesControllerTests.mjs
  29. 42 43
      services/web/test/unit/src/References/ReferencesHandlerTests.mjs
  30. 19 21
      services/web/test/unit/src/Spelling/SpellingControllerTests.mjs
  31. 13 11
      services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs
  32. 26 22
      services/web/test/unit/src/Subscription/TeamInvitesControllerTests.mjs
  33. 12 13
      services/web/test/unit/src/Tags/TagsControllerTests.mjs
  34. 28 20
      services/web/test/unit/src/ThirdPartyDataStore/TpdsControllerTests.mjs
  35. 27 20
      services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateHandlerTests.mjs
  36. 58 32
      services/web/test/unit/src/TokenAccess/TokenAccessControllerTests.mjs
  37. 27 26
      services/web/test/unit/src/Uploads/ProjectUploadControllerTests.mjs
  38. 34 30
      services/web/test/unit/src/User/UserPagesControllerTests.mjs
  39. 27 25
      services/web/test/unit/src/UserMembership/UserMembershipControllerTests.mjs

+ 19 - 20
services/web/test/unit/src/Analytics/AnalyticsControllerTests.js → services/web/test/unit/src/Analytics/AnalyticsControllerTests.mjs

@@ -1,14 +1,13 @@
-const SandboxedModule = require('sandboxed-module')
-const path = require('path')
-const modulePath = path.join(
-  __dirname,
-  '../../../../app/src/Features/Analytics/AnalyticsController'
-)
-const sinon = require('sinon')
-const MockResponse = require('../helpers/MockResponse')
+import esmock from 'esmock'
+import sinon from 'sinon'
+import MockResponse from '../helpers/MockResponse.js'
+const modulePath = new URL(
+  '../../../../app/src/Features/Analytics/AnalyticsController.mjs',
+  import.meta.url
+).pathname
 
 
 describe('AnalyticsController', function () {
 describe('AnalyticsController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.SessionManager = { getLoggedInUserId: sinon.stub() }
     this.SessionManager = { getLoggedInUserId: sinon.stub() }
 
 
     this.AnalyticsManager = {
     this.AnalyticsManager = {
@@ -20,17 +19,17 @@ describe('AnalyticsController', function () {
       hasFeature: sinon.stub().returns(true),
       hasFeature: sinon.stub().returns(true),
     }
     }
 
 
-    this.controller = SandboxedModule.require(modulePath, {
-      requires: {
-        './AnalyticsManager': this.AnalyticsManager,
-        '../Authentication/SessionManager': this.SessionManager,
-        '../../infrastructure/Features': this.Features,
-        '../../infrastructure/GeoIpLookup': (this.GeoIpLookup = {
-          promises: {
-            getDetails: sinon.stub().resolves(),
-          },
-        }),
-      },
+    this.controller = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Analytics/AnalyticsManager.js':
+        this.AnalyticsManager,
+      '../../../../app/src/Features/Authentication/SessionManager.js':
+        this.SessionManager,
+      '../../../../app/src/infrastructure/Features.js': this.Features,
+      '../../../../app/src/infrastructure/GeoIpLookup.js': (this.GeoIpLookup = {
+        promises: {
+          getDetails: sinon.stub().resolves(),
+        },
+      }),
     })
     })
 
 
     this.res = new MockResponse()
     this.res = new MockResponse()

+ 16 - 18
services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddlewareTests.js → services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddlewareTests.mjs

@@ -1,17 +1,16 @@
-const SandboxedModule = require('sandboxed-module')
-const path = require('path')
-const sinon = require('sinon')
-const MockRequest = require('../helpers/MockRequest')
-const MockResponse = require('../helpers/MockResponse')
-const { assert } = require('chai')
-
-const MODULE_PATH = path.join(
-  __dirname,
-  '../../../../app/src/Features/Analytics/AnalyticsUTMTrackingMiddleware'
-)
+import esmock from 'esmock'
+import sinon from 'sinon'
+import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.js'
+import { assert } from 'chai'
+
+const MODULE_PATH = new URL(
+  '../../../../app/src/Features/Analytics/AnalyticsUTMTrackingMiddleware',
+  import.meta.url
+).pathname
 
 
 describe('AnalyticsUTMTrackingMiddleware', function () {
 describe('AnalyticsUTMTrackingMiddleware', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.analyticsId = 'ecdb935a-52f3-4f91-aebc-7a70d2ffbb55'
     this.analyticsId = 'ecdb935a-52f3-4f91-aebc-7a70d2ffbb55'
     this.userId = '61795fcb013504bb7b663092'
     this.userId = '61795fcb013504bb7b663092'
 
 
@@ -25,15 +24,14 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
       },
       },
     }
     }
 
 
-    this.AnalyticsUTMTrackingMiddleware = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        './AnalyticsManager': (this.AnalyticsManager = {
+    this.AnalyticsUTMTrackingMiddleware = await esmock.strict(MODULE_PATH, {
+      '../../../../app/src/Features/Analytics/AnalyticsManager.js':
+        (this.AnalyticsManager = {
           recordEventForSession: sinon.stub().resolves(),
           recordEventForSession: sinon.stub().resolves(),
           setUserPropertyForSessionInBackground: sinon.stub(),
           setUserPropertyForSessionInBackground: sinon.stub(),
         }),
         }),
-        '@overleaf/settings': {
-          siteUrl: 'https://www.overleaf.com',
-        },
+      '@overleaf/settings': {
+        siteUrl: 'https://www.overleaf.com',
       },
       },
     })
     })
 
 

+ 36 - 25
services/web/test/unit/src/Collaborators/CollaboratorsControllerTests.js → services/web/test/unit/src/Collaborators/CollaboratorsControllerTests.mjs

@@ -1,16 +1,18 @@
-const sinon = require('sinon')
-const { expect } = require('chai')
-const SandboxedModule = require('sandboxed-module')
-const { ObjectId } = require('mongodb-legacy')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
-const MockRequest = require('../helpers/MockRequest')
-const MockResponse = require('../helpers/MockResponse')
+import sinon from 'sinon'
+import { expect } from 'chai'
+import esmock from 'esmock'
+import mongodb from 'mongodb-legacy'
+import Errors from '../../../../app/src/Features/Errors/Errors.js'
+import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.js'
+
+const ObjectId = mongodb.ObjectId
 
 
 const MODULE_PATH =
 const MODULE_PATH =
-  '../../../../app/src/Features/Collaborators/CollaboratorsController.js'
+  '../../../../app/src/Features/Collaborators/CollaboratorsController.mjs'
 
 
 describe('CollaboratorsController', function () {
 describe('CollaboratorsController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.res = new MockResponse()
     this.res = new MockResponse()
     this.req = new MockRequest()
     this.req = new MockRequest()
 
 
@@ -77,22 +79,31 @@ describe('CollaboratorsController', function () {
       },
       },
     }
     }
 
 
-    this.CollaboratorsController = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        'mongodb-legacy': { ObjectId },
-        './CollaboratorsHandler': this.CollaboratorsHandler,
-        './CollaboratorsGetter': this.CollaboratorsGetter,
-        './OwnershipTransferHandler': this.OwnershipTransferHandler,
-        '../Editor/EditorRealTimeController': this.EditorRealTimeController,
-        '../../Features/Errors/HttpErrorHandler': this.HttpErrorHandler,
-        '../Tags/TagsHandler': this.TagsHandler,
-        '../Authentication/SessionManager': this.SessionManager,
-        '../TokenAccess/TokenAccessHandler': this.TokenAccessHandler,
-        '../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
-        '../Project/ProjectGetter': this.ProjectGetter,
-        '../SplitTests/SplitTestHandler': this.SplitTestHandler,
-        '../Subscription/LimitationsManager': this.LimitationsManager,
-      },
+    this.CollaboratorsController = await esmock.strict(MODULE_PATH, {
+      'mongodb-legacy': { ObjectId },
+      '../../../../app/src/Features/Collaborators/CollaboratorsHandler.js':
+        this.CollaboratorsHandler,
+      '../../../../app/src/Features/Collaborators/CollaboratorsGetter.js':
+        this.CollaboratorsGetter,
+      '../../../../app/src/Features/Collaborators/OwnershipTransferHandler.js':
+        this.OwnershipTransferHandler,
+      '../../../../app/src/Features/Editor/EditorRealTimeController':
+        this.EditorRealTimeController,
+      '../../../../app/src/Features/Errors/HttpErrorHandler.js':
+        this.HttpErrorHandler,
+      '../../../../app/src/Features/Tags/TagsHandler.js': this.TagsHandler,
+      '../../../../app/src/Features/Authentication/SessionManager.js':
+        this.SessionManager,
+      '../../../../app/src/Features/TokenAccess/TokenAccessHandler.js':
+        this.TokenAccessHandler,
+      '../../../../app/src/Features/Project/ProjectAuditLogHandler.js':
+        this.ProjectAuditLogHandler,
+      '../../../../app/src/Features/Project/ProjectGetter.js':
+        this.ProjectGetter,
+      '../../../../app/src/Features/SplitTests/SplitTestHandler.js':
+        this.SplitTestHandler,
+      '../../../../app/src/Features/Subscription/LimitationsManager.js':
+        this.LimitationsManager,
     })
     })
   })
   })
 
 

+ 38 - 28
services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.js → services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.mjs

@@ -1,17 +1,19 @@
-const sinon = require('sinon')
-const { expect } = require('chai')
-const SandboxedModule = require('sandboxed-module')
-const MockRequest = require('../helpers/MockRequest')
-const MockResponse = require('../helpers/MockResponse')
-const { ObjectId } = require('mongodb-legacy')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
-const _ = require('lodash')
+import sinon from 'sinon'
+import { expect } from 'chai'
+import esmock from 'esmock'
+import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.js'
+import mongodb from 'mongodb-legacy'
+import Errors from '../../../../app/src/Features/Errors/Errors.js'
+import _ from 'lodash'
+
+const ObjectId = mongodb.ObjectId
 
 
 const MODULE_PATH =
 const MODULE_PATH =
-  '../../../../app/src/Features/Collaborators/CollaboratorsInviteController.js'
+  '../../../../app/src/Features/Collaborators/CollaboratorsInviteController.mjs'
 
 
 describe('CollaboratorsInviteController', function () {
 describe('CollaboratorsInviteController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.projectId = 'project-id-123'
     this.projectId = 'project-id-123'
     this.token = 'some-opaque-token'
     this.token = 'some-opaque-token'
     this.tokenHmac = 'some-hmac-token'
     this.tokenHmac = 'some-hmac-token'
@@ -120,24 +122,32 @@ describe('CollaboratorsInviteController', function () {
       },
       },
     }
     }
 
 
-    this.CollaboratorsInviteController = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        '../Project/ProjectGetter': this.ProjectGetter,
-        '../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
-        '../Subscription/LimitationsManager': this.LimitationsManager,
-        '../User/UserGetter': this.UserGetter,
-        './CollaboratorsGetter': this.CollaboratorsGetter,
-        './CollaboratorsInviteHandler': this.CollaboratorsInviteHandler,
-        './CollaboratorsInviteGetter': this.CollaboratorsInviteGetter,
-        '../Editor/EditorRealTimeController': this.EditorRealTimeController,
-        '../Analytics/AnalyticsManager': this.AnalyticsManger,
-        '../Authentication/SessionManager': this.SessionManager,
-        '@overleaf/settings': this.settings,
-        '../../infrastructure/RateLimiter': this.RateLimiter,
-        '../Authentication/AuthenticationController':
-          this.AuthenticationController,
-        '../SplitTests/SplitTestHandler': this.SplitTestHandler,
-      },
+    this.CollaboratorsInviteController = await esmock.strict(MODULE_PATH, {
+      '../../../../app/src/Features/Project/ProjectGetter.js':
+        this.ProjectGetter,
+      '../../../../app/src/Features/Project/ProjectAuditLogHandler.js':
+        this.ProjectAuditLogHandler,
+      '../../../../app/src/Features/Subscription/LimitationsManager.js':
+        this.LimitationsManager,
+      '../../../../app/src/Features/User/UserGetter.js': this.UserGetter,
+      '../../../../app/src/Features/Collaborators/CollaboratorsGetter.js':
+        this.CollaboratorsGetter,
+      '../../../../app/src/Features/Collaborators/CollaboratorsInviteHandler.mjs':
+        this.CollaboratorsInviteHandler,
+      '../../../../app/src/Features/Collaborators/CollaboratorsInviteGetter.js':
+        this.CollaboratorsInviteGetter,
+      '../../../../app/src/Features/Editor/EditorRealTimeController.js':
+        this.EditorRealTimeController,
+      '../../../../app/src/Features/Analytics/AnalyticsManager.js':
+        this.AnalyticsManger,
+      '../../../../app/src/Features/Authentication/SessionManager.js':
+        this.SessionManager,
+      '@overleaf/settings': this.settings,
+      '../../../../app/src/infrastructure/RateLimiter': this.RateLimiter,
+      '../../../../app/src/Features/Authentication/AuthenticationController':
+        this.AuthenticationController,
+      '../../../../app/src/Features/SplitTests/SplitTestHandler':
+        this.SplitTestHandler,
     })
     })
 
 
     this.res = new MockResponse()
     this.res = new MockResponse()

+ 33 - 22
services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.js → services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.mjs

@@ -1,14 +1,16 @@
-const sinon = require('sinon')
-const { expect } = require('chai')
-const SandboxedModule = require('sandboxed-module')
-const { ObjectId } = require('mongodb-legacy')
-const Crypto = require('crypto')
+import sinon from 'sinon'
+import { expect } from 'chai'
+import esmock from 'esmock'
+import mongodb from 'mongodb-legacy'
+import Crypto from 'crypto'
+
+const ObjectId = mongodb.ObjectId
 
 
 const MODULE_PATH =
 const MODULE_PATH =
-  '../../../../app/src/Features/Collaborators/CollaboratorsInviteHandler.js'
+  '../../../../app/src/Features/Collaborators/CollaboratorsInviteHandler.mjs'
 
 
 describe('CollaboratorsInviteHandler', function () {
 describe('CollaboratorsInviteHandler', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.ProjectInvite = class ProjectInvite {
     this.ProjectInvite = class ProjectInvite {
       constructor(options) {
       constructor(options) {
         if (options == null) {
         if (options == null) {
@@ -72,22 +74,31 @@ describe('CollaboratorsInviteHandler', function () {
       addEntryInBackground: sinon.stub(),
       addEntryInBackground: sinon.stub(),
     }
     }
 
 
-    this.CollaboratorsInviteHandler = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        '@overleaf/settings': this.settings,
-        '../../models/ProjectInvite': { ProjectInvite: this.ProjectInvite },
-        './CollaboratorsEmailHandler': this.CollaboratorsEmailHandler,
-        './CollaboratorsHandler': this.CollaboratorsHandler,
-        '../User/UserGetter': this.UserGetter,
-        '../Project/ProjectGetter': this.ProjectGetter,
-        '../Notifications/NotificationsBuilder': this.NotificationsBuilder,
-        './CollaboratorsInviteHelper': this.CollaboratorsInviteHelper,
-        './CollaboratorsInviteGetter': this.CollaboratorsInviteGetter,
-        '../SplitTests/SplitTestHandler': this.SplitTestHandler,
-        '../Subscription/LimitationsManager': this.LimitationsManager,
-        '../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
-        crypto: this.CryptogetAssignmentForUser,
+    this.CollaboratorsInviteHandler = await esmock.strict(MODULE_PATH, {
+      '@overleaf/settings': this.settings,
+      '../../../../app/src/models/ProjectInvite.js': {
+        ProjectInvite: this.ProjectInvite,
       },
       },
+      '../../../../app/src/Features/Collaborators/CollaboratorsEmailHandler.mjs':
+        this.CollaboratorsEmailHandler,
+      '../../../../app/src/Features/Collaborators/CollaboratorsHandler.js':
+        this.CollaboratorsHandler,
+      '../../../../app/src/Features/User/UserGetter.js': this.UserGetter,
+      '../../../../app/src/Features/Project/ProjectGetter.js':
+        this.ProjectGetter,
+      '../../../../app/src/Features/Notifications/NotificationsBuilder.js':
+        this.NotificationsBuilder,
+      '../../../../app/src/Features/Collaborators/CollaboratorsInviteHelper.js':
+        this.CollaboratorsInviteHelper,
+      '../../../../app/src/Features/Collaborators/CollaboratorsInviteGetter':
+        this.CollaboratorsInviteGetter,
+      '../../../../app/src/Features/SplitTests/SplitTestHandler.js':
+        this.SplitTestHandler,
+      '../../../../app/src/Features/Subscription/LimitationsManager.js':
+        this.LimitationsManager,
+      '../../../../app/src/Features/Project/ProjectAuditLogHandler.js':
+        this.ProjectAuditLogHandler,
+      crypto: this.CryptogetAssignmentForUser,
     })
     })
 
 
     this.projectId = new ObjectId()
     this.projectId = new ObjectId()

+ 17 - 15
services/web/test/unit/src/Contact/ContactControllerTests.js → services/web/test/unit/src/Contact/ContactControllerTests.mjs

@@ -1,21 +1,23 @@
-const sinon = require('sinon')
-const { expect } = require('chai')
-const modulePath = '../../../../app/src/Features/Contacts/ContactController.js'
-const SandboxedModule = require('sandboxed-module')
-const MockResponse = require('../helpers/MockResponse')
+import sinon from 'sinon'
+import { expect } from 'chai'
+import esmock from 'esmock'
+import MockResponse from '../helpers/MockResponse.js'
+const modulePath = '../../../../app/src/Features/Contacts/ContactController.mjs'
 
 
 describe('ContactController', function () {
 describe('ContactController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.SessionManager = { getLoggedInUserId: sinon.stub() }
     this.SessionManager = { getLoggedInUserId: sinon.stub() }
-    this.ContactController = SandboxedModule.require(modulePath, {
-      requires: {
-        '../User/UserGetter': (this.UserGetter = { promises: {} }),
-        './ContactManager': (this.ContactManager = { promises: {} }),
-        '../Authentication/SessionManager': (this.SessionManager = {}),
-        '../../infrastructure/Modules': (this.Modules = {
-          promises: { hooks: {} },
-        }),
-      },
+    this.ContactController = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/User/UserGetter': (this.UserGetter = {
+        promises: {},
+      }),
+      '../../../../app/src/Features/Contacts/ContactManager':
+        (this.ContactManager = { promises: {} }),
+      '../../../../app/src/Features/Authentication/SessionManager':
+        (this.SessionManager = {}),
+      '../../../../app/src/infrastructure/Modules': (this.Modules = {
+        promises: { hooks: {} },
+      }),
     })
     })
 
 
     this.req = {}
     this.req = {}

+ 11 - 12
services/web/test/unit/src/Cooldown/CooldownMiddlewareTests.js → services/web/test/unit/src/Cooldown/CooldownMiddlewareTests.mjs

@@ -9,21 +9,20 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const { expect } = require('chai')
-const modulePath = require('path').join(
-  __dirname,
-  '../../../../app/src/Features/Cooldown/CooldownMiddleware'
-)
+import esmock from 'esmock'
+import sinon from 'sinon'
+import { expect } from 'chai'
+const modulePath = new URL(
+  '../../../../app/src/Features/Cooldown/CooldownMiddleware.mjs',
+  import.meta.url
+).pathname
 
 
 describe('CooldownMiddleware', function () {
 describe('CooldownMiddleware', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.CooldownManager = { isProjectOnCooldown: sinon.stub() }
     this.CooldownManager = { isProjectOnCooldown: sinon.stub() }
-    return (this.CooldownMiddleware = SandboxedModule.require(modulePath, {
-      requires: {
-        './CooldownManager': this.CooldownManager,
-      },
+    return (this.CooldownMiddleware = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Cooldown/CooldownManager.js':
+        this.CooldownManager,
     }))
     }))
   })
   })
 
 

+ 12 - 12
services/web/test/unit/src/DocumentUpdater/DocumentUpdaterControllerTests.js → services/web/test/unit/src/DocumentUpdater/DocumentUpdaterControllerTests.mjs

@@ -1,13 +1,13 @@
-const sinon = require('sinon')
-const { expect } = require('chai')
-const SandboxedModule = require('sandboxed-module')
-const MockResponse = require('../helpers/MockResponse')
+import sinon from 'sinon'
+import { expect } from 'chai'
+import esmock from 'esmock'
+import MockResponse from '../helpers/MockResponse.js'
 
 
 const MODULE_PATH =
 const MODULE_PATH =
-  '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterController.js'
+  '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterController.mjs'
 
 
 describe('DocumentUpdaterController', function () {
 describe('DocumentUpdaterController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.DocumentUpdaterHandler = {
     this.DocumentUpdaterHandler = {
       promises: {
       promises: {
         getDocument: sinon.stub(),
         getDocument: sinon.stub(),
@@ -18,12 +18,12 @@ describe('DocumentUpdaterController', function () {
         findElement: sinon.stub(),
         findElement: sinon.stub(),
       },
       },
     }
     }
-    this.controller = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        '@overleaf/settings': this.settings,
-        '../Project/ProjectLocator': this.ProjectLocator,
-        './DocumentUpdaterHandler': this.DocumentUpdaterHandler,
-      },
+    this.controller = await esmock.strict(MODULE_PATH, {
+      '@overleaf/settings': this.settings,
+      '../../../../app/src/Features/Project/ProjectLocator.js':
+        this.ProjectLocator,
+      '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler.js':
+        this.DocumentUpdaterHandler,
     })
     })
     this.projectId = '2k3j1lk3j21lk3j'
     this.projectId = '2k3j1lk3j21lk3j'
     this.fileId = '12321kklj1lk3jk12'
     this.fileId = '12321kklj1lk3jk12'

+ 16 - 16
services/web/test/unit/src/Documents/DocumentControllerTests.js → services/web/test/unit/src/Documents/DocumentControllerTests.mjs

@@ -1,14 +1,14 @@
-const sinon = require('sinon')
-const SandboxedModule = require('sandboxed-module')
-const MockRequest = require('../helpers/MockRequest')
-const MockResponse = require('../helpers/MockResponse')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
+import sinon from 'sinon'
+import esmock from 'esmock'
+import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.js'
+import Errors from '../../../../app/src/Features/Errors/Errors.js'
 
 
 const MODULE_PATH =
 const MODULE_PATH =
-  '../../../../app/src/Features/Documents/DocumentController.js'
+  '../../../../app/src/Features/Documents/DocumentController.mjs'
 
 
 describe('DocumentController', function () {
 describe('DocumentController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.res = new MockResponse()
     this.res = new MockResponse()
     this.req = new MockRequest()
     this.req = new MockRequest()
     this.next = sinon.stub()
     this.next = sinon.stub()
@@ -87,15 +87,15 @@ describe('DocumentController', function () {
       },
       },
     }
     }
 
 
-    this.DocumentController = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        '../Project/ProjectGetter': this.ProjectGetter,
-        '../Project/ProjectLocator': this.ProjectLocator,
-        '../Project/ProjectEntityHandler': this.ProjectEntityHandler,
-        '../Project/ProjectEntityUpdateHandler':
-          this.ProjectEntityUpdateHandler,
-        '../Chat/ChatApiHandler': this.ChatApiHandler,
-      },
+    this.DocumentController = await esmock.strict(MODULE_PATH, {
+      '../../../../app/src/Features/Project/ProjectGetter': this.ProjectGetter,
+      '../../../../app/src/Features/Project/ProjectLocator':
+        this.ProjectLocator,
+      '../../../../app/src/Features/Project/ProjectEntityHandler':
+        this.ProjectEntityHandler,
+      '../../../../app/src/Features/Project/ProjectEntityUpdateHandler':
+        this.ProjectEntityUpdateHandler,
+      '../../../../app/src/Features/Chat/ChatApiHandler': this.ChatApiHandler,
     })
     })
   })
   })
 
 

+ 15 - 24
services/web/test/unit/src/Downloads/ProjectDownloadsControllerTests.js → services/web/test/unit/src/Downloads/ProjectDownloadsControllerTests.mjs

@@ -1,8 +1,3 @@
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
 // TODO: This file was created by bulk-decaffeinate.
 // TODO: This file was created by bulk-decaffeinate.
 // Fix any style issues and re-enable lint.
 // Fix any style issues and re-enable lint.
 /*
 /*
@@ -10,33 +5,29 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
-const sinon = require('sinon')
-const { expect } = require('chai')
+import sinon from 'sinon'
+import esmock from 'esmock'
+import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.js'
 const modulePath =
 const modulePath =
-  '../../../../app/src/Features/Downloads/ProjectDownloadsController.js'
-const SandboxedModule = require('sandboxed-module')
-const MockRequest = require('../helpers/MockRequest')
-const MockResponse = require('../helpers/MockResponse')
+  '../../../../app/src/Features/Downloads/ProjectDownloadsController.mjs'
 
 
 describe('ProjectDownloadsController', function () {
 describe('ProjectDownloadsController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.project_id = 'project-id-123'
     this.project_id = 'project-id-123'
     this.req = new MockRequest()
     this.req = new MockRequest()
     this.res = new MockResponse()
     this.res = new MockResponse()
     this.next = sinon.stub()
     this.next = sinon.stub()
     this.DocumentUpdaterHandler = sinon.stub()
     this.DocumentUpdaterHandler = sinon.stub()
-    return (this.ProjectDownloadsController = SandboxedModule.require(
-      modulePath,
-      {
-        requires: {
-          './ProjectZipStreamManager': (this.ProjectZipStreamManager = {}),
-          '../Project/ProjectGetter': (this.ProjectGetter = {}),
-          '@overleaf/metrics': (this.metrics = {}),
-          '../DocumentUpdater/DocumentUpdaterHandler':
-            this.DocumentUpdaterHandler,
-        },
-      }
-    ))
+    return (this.ProjectDownloadsController = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Downloads/ProjectZipStreamManager.mjs':
+        (this.ProjectZipStreamManager = {}),
+      '../../../../app/src/Features/Project/ProjectGetter.js':
+        (this.ProjectGetter = {}),
+      '@overleaf/metrics': (this.metrics = {}),
+      '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler.js':
+        this.DocumentUpdaterHandler,
+    }))
   })
   })
 
 
   describe('downloadProject', function () {
   describe('downloadProject', function () {

+ 21 - 22
services/web/test/unit/src/Downloads/ProjectZipStreamManagerTests.js → services/web/test/unit/src/Downloads/ProjectZipStreamManagerTests.mjs

@@ -1,9 +1,3 @@
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-    one-var,
-*/
 // TODO: This file was created by bulk-decaffeinate.
 // TODO: This file was created by bulk-decaffeinate.
 // Fix any style issues and re-enable lint.
 // Fix any style issues and re-enable lint.
 /*
 /*
@@ -14,28 +8,34 @@
  * DS205: Consider reworking code to avoid use of IIFEs
  * DS205: Consider reworking code to avoid use of IIFEs
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
-const sinon = require('sinon')
-const { expect } = require('chai')
+import sinon from 'sinon'
+import esmock from 'esmock'
+import { EventEmitter } from 'events'
 const modulePath =
 const modulePath =
-  '../../../../app/src/Features/Downloads/ProjectZipStreamManager.js'
-const SandboxedModule = require('sandboxed-module')
-const { EventEmitter } = require('events')
+  '../../../../app/src/Features/Downloads/ProjectZipStreamManager.mjs'
 
 
 describe('ProjectZipStreamManager', function () {
 describe('ProjectZipStreamManager', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.project_id = 'project-id-123'
     this.project_id = 'project-id-123'
     this.callback = sinon.stub()
     this.callback = sinon.stub()
     this.archive = {
     this.archive = {
       on() {},
       on() {},
       append: sinon.stub(),
       append: sinon.stub(),
     }
     }
-    return (this.ProjectZipStreamManager = SandboxedModule.require(modulePath, {
-      requires: {
-        archiver: (this.archiver = sinon.stub().returns(this.archive)),
-        '../Project/ProjectEntityHandler': (this.ProjectEntityHandler = {}),
-        '../FileStore/FileStoreHandler': (this.FileStoreHandler = {}),
-        '../Project/ProjectGetter': (this.ProjectGetter = {}),
-      },
+    this.logger = {
+      error: sinon.stub(),
+      info: sinon.stub(),
+      debug: sinon.stub(),
+    }
+    return (this.ProjectZipStreamManager = await esmock.strict(modulePath, {
+      archiver: (this.archiver = sinon.stub().returns(this.archive)),
+      '@overleaf/logger': this.logger,
+      '../../../../app/src/Features/Project/ProjectEntityHandler':
+        (this.ProjectEntityHandler = {}),
+      '../../../../app/src/Features/FileStore/FileStoreHandler':
+        (this.FileStoreHandler = {}),
+      '../../../../app/src/Features/Project/ProjectGetter':
+        (this.ProjectGetter = {}),
     }))
     }))
   })
   })
 
 
@@ -254,7 +254,7 @@ describe('ProjectZipStreamManager', function () {
           .stub()
           .stub()
           .callsArg(2)
           .callsArg(2)
         this.archive.finalize = sinon.stub()
         this.archive.finalize = sinon.stub()
-        return this.ProjectZipStreamManager.createZipStreamForProject(
+        this.ProjectZipStreamManager.createZipStreamForProject(
           this.project_id,
           this.project_id,
           this.callback
           this.callback
         )
         )
@@ -379,8 +379,7 @@ describe('ProjectZipStreamManager', function () {
         .stub()
         .stub()
         .callsArgWith(1, null, this.files)
         .callsArgWith(1, null, this.files)
       this.FileStoreHandler.getFileStream = (projectId, fileId, ...rest) => {
       this.FileStoreHandler.getFileStream = (projectId, fileId, ...rest) => {
-        const obj = rest[0],
-          callback = rest[1]
+        const [, callback] = rest
         return callback(null, this.streams[fileId])
         return callback(null, this.streams[fileId])
       }
       }
       sinon.spy(this.FileStoreHandler, 'getFileStream')
       sinon.spy(this.FileStoreHandler, 'getFileStream')

+ 22 - 31
services/web/test/unit/src/Exports/ExportsControllerTests.js → services/web/test/unit/src/Exports/ExportsControllerTests.mjs

@@ -1,9 +1,3 @@
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-    no-useless-escape,
-*/
 // TODO: This file was created by bulk-decaffeinate.
 // TODO: This file was created by bulk-decaffeinate.
 // Fix any style issues and re-enable lint.
 // Fix any style issues and re-enable lint.
 /*
 /*
@@ -11,14 +5,13 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
-const SandboxedModule = require('sandboxed-module')
-const assert = require('assert')
-const { expect } = require('chai')
-const sinon = require('sinon')
-const modulePath = require('path').join(
-  __dirname,
-  '../../../../app/src/Features/Exports/ExportsController.js'
-)
+import esmock from 'esmock'
+import { expect } from 'chai'
+import sinon from 'sinon'
+const modulePath = new URL(
+  '../../../../app/src/Features/Exports/ExportsController.mjs',
+  import.meta.url
+).pathname
 
 
 describe('ExportsController', function () {
 describe('ExportsController', function () {
   const projectId = '123njdskj9jlk'
   const projectId = '123njdskj9jlk'
@@ -32,7 +25,7 @@ describe('ExportsController', function () {
   const license = 'other'
   const license = 'other'
   const showSource = true
   const showSource = true
 
 
-  beforeEach(function () {
+  beforeEach(async function () {
     this.handler = { getUserNotifications: sinon.stub().callsArgWith(1) }
     this.handler = { getUserNotifications: sinon.stub().callsArgWith(1) }
     this.req = {
     this.req = {
       params: {
       params: {
@@ -61,12 +54,10 @@ describe('ExportsController', function () {
     this.AuthenticationController = {
     this.AuthenticationController = {
       getLoggedInUserId: sinon.stub().returns(this.req.session.user._id),
       getLoggedInUserId: sinon.stub().returns(this.req.session.user._id),
     }
     }
-    return (this.controller = SandboxedModule.require(modulePath, {
-      requires: {
-        './ExportsHandler': this.handler,
-        '../Authentication/AuthenticationController':
-          this.AuthenticationController,
-      },
+    return (this.controller = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Exports/ExportsHandler.mjs': this.handler,
+      '../../../../app/src/Features/Authentication/AuthenticationController.js':
+        this.AuthenticationController,
     }))
     }))
   })
   })
 
 
@@ -170,16 +161,16 @@ describe('ExportsController', function () {
   it('should ask the handler to return the status of an export', function (done) {
   it('should ask the handler to return the status of an export', function (done) {
     this.handler.fetchExport = sinon.stub().yields(
     this.handler.fetchExport = sinon.stub().yields(
       null,
       null,
-      `{ \
-\"id\":897, \
-\"status_summary\":\"completed\", \
-\"status_detail\":\"all done\", \
-\"partner_submission_id\":\"abc123\", \
-\"v2_user_email\":\"la@tex.com\", \
-\"v2_user_first_name\":\"Arthur\", \
-\"v2_user_last_name\":\"Author\", \
-\"title\":\"my project\", \
-\"token\":\"token\" \
+      `{
+"id":897,
+"status_summary":"completed",
+"status_detail":"all done",
+"partner_submission_id":"abc123",
+"v2_user_email":"la@tex.com",
+"v2_user_first_name":"Arthur",
+"v2_user_last_name":"Author",
+"title":"my project",
+"token":"token"
 }`
 }`
     )
     )
 
 

+ 17 - 20
services/web/test/unit/src/Exports/ExportsHandlerTests.js → services/web/test/unit/src/Exports/ExportsHandlerTests.mjs

@@ -1,8 +1,3 @@
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
 // TODO: This file was created by bulk-decaffeinate.
 // TODO: This file was created by bulk-decaffeinate.
 // Fix any style issues and re-enable lint.
 // Fix any style issues and re-enable lint.
 /*
 /*
@@ -10,29 +5,31 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
-const sinon = require('sinon')
-const { expect } = require('chai')
-const modulePath = '../../../../app/src/Features/Exports/ExportsHandler.js'
-const SandboxedModule = require('sandboxed-module')
+import sinon from 'sinon'
+import esmock from 'esmock'
+import { expect } from 'chai'
+const modulePath = '../../../../app/src/Features/Exports/ExportsHandler.mjs'
 
 
 describe('ExportsHandler', function () {
 describe('ExportsHandler', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.stubRequest = {}
     this.stubRequest = {}
     this.request = {
     this.request = {
       defaults: () => {
       defaults: () => {
         return this.stubRequest
         return this.stubRequest
       },
       },
     }
     }
-    this.ExportsHandler = SandboxedModule.require(modulePath, {
-      requires: {
-        '../Project/ProjectGetter': (this.ProjectGetter = {}),
-        '../Project/ProjectHistoryHandler': (this.ProjectHistoryHandler = {}),
-        '../Project/ProjectLocator': (this.ProjectLocator = {}),
-        '../Project/ProjectRootDocManager': (this.ProjectRootDocManager = {}),
-        '../User/UserGetter': (this.UserGetter = {}),
-        '@overleaf/settings': (this.settings = {}),
-        request: this.request,
-      },
+    this.ExportsHandler = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Project/ProjectGetter':
+        (this.ProjectGetter = {}),
+      '../../../../app/src/Features/Project/ProjectHistoryHandler':
+        (this.ProjectHistoryHandler = {}),
+      '../../../../app/src/Features/Project/ProjectLocator':
+        (this.ProjectLocator = {}),
+      '../../../../app/src/Features/Project/ProjectRootDocManager':
+        (this.ProjectRootDocManager = {}),
+      '../../../../app/src/Features/User/UserGetter': (this.UserGetter = {}),
+      '@overleaf/settings': (this.settings = {}),
+      request: this.request,
     })
     })
     this.project_id = 'project-id-123'
     this.project_id = 'project-id-123'
     this.project_history_id = 987
     this.project_history_id = 987

+ 13 - 13
services/web/test/unit/src/FileStore/FileStoreControllerTests.js → services/web/test/unit/src/FileStore/FileStoreControllerTests.mjs

@@ -1,29 +1,29 @@
-const { expect } = require('chai')
-const sinon = require('sinon')
-const SandboxedModule = require('sandboxed-module')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
-const MockResponse = require('../helpers/MockResponse')
+import { expect } from 'chai'
+import sinon from 'sinon'
+import esmock from 'esmock'
+import Errors from '../../../../app/src/Features/Errors/Errors.js'
+import MockResponse from '../helpers/MockResponse.js'
 
 
 const MODULE_PATH =
 const MODULE_PATH =
-  '../../../../app/src/Features/FileStore/FileStoreController.js'
+  '../../../../app/src/Features/FileStore/FileStoreController.mjs'
 
 
 const expectedFileHeaders = {
 const expectedFileHeaders = {
   'Cache-Control': 'private, max-age=3600',
   'Cache-Control': 'private, max-age=3600',
 }
 }
 
 
 describe('FileStoreController', function () {
 describe('FileStoreController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.FileStoreHandler = {
     this.FileStoreHandler = {
       getFileStream: sinon.stub(),
       getFileStream: sinon.stub(),
       getFileSize: sinon.stub(),
       getFileSize: sinon.stub(),
     }
     }
     this.ProjectLocator = { findElement: sinon.stub() }
     this.ProjectLocator = { findElement: sinon.stub() }
-    this.controller = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        '@overleaf/settings': this.settings,
-        '../Project/ProjectLocator': this.ProjectLocator,
-        './FileStoreHandler': this.FileStoreHandler,
-      },
+    this.controller = await esmock.strict(MODULE_PATH, {
+      '@overleaf/settings': this.settings,
+      '../../../../app/src/Features/Project/ProjectLocator':
+        this.ProjectLocator,
+      '../../../../app/src/Features/FileStore/FileStoreHandler':
+        this.FileStoreHandler,
     })
     })
     this.stream = {}
     this.stream = {}
     this.projectId = '2k3j1lk3j21lk3j'
     this.projectId = '2k3j1lk3j21lk3j'

+ 30 - 22
services/web/test/unit/src/LinkedFiles/LinkedFilesControllerTests.js → services/web/test/unit/src/LinkedFiles/LinkedFilesControllerTests.mjs

@@ -1,8 +1,8 @@
-const { expect } = require('chai')
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
+import { expect } from 'chai'
+import esmock from 'esmock'
+import sinon from 'sinon'
 const modulePath =
 const modulePath =
-  '../../../../app/src/Features/LinkedFiles/LinkedFilesController'
+  '../../../../app/src/Features/LinkedFiles/LinkedFilesController.mjs'
 
 
 describe('LinkedFilesController', function () {
 describe('LinkedFilesController', function () {
   beforeEach(function () {
   beforeEach(function () {
@@ -14,7 +14,7 @@ describe('LinkedFilesController', function () {
     this.clock.restore()
     this.clock.restore()
   })
   })
 
 
-  beforeEach(function () {
+  beforeEach(async function () {
     this.userId = 'user-id'
     this.userId = 'user-id'
     this.Agent = {
     this.Agent = {
       promises: {
       promises: {
@@ -42,24 +42,32 @@ describe('LinkedFilesController', function () {
     this.ProjectOutputFileAgent = {}
     this.ProjectOutputFileAgent = {}
     this.EditorController = {}
     this.EditorController = {}
     this.ProjectLocator = {}
     this.ProjectLocator = {}
-    this.logger = {}
+    this.logger = {
+      error: sinon.stub(),
+    }
     this.settings = { enabledLinkedFileTypes: [] }
     this.settings = { enabledLinkedFileTypes: [] }
-    this.LinkedFilesController = SandboxedModule.require(modulePath, {
-      requires: {
-        '../Authentication/SessionManager': this.SessionManager,
-        '../../../../app/src/Features/Analytics/AnalyticsManager':
-          this.AnalyticsManager,
-        './LinkedFilesHandler': this.LinkedFilesHandler,
-        '../Editor/EditorRealTimeController': this.EditorRealTimeController,
-        '../References/ReferencesHandler': this.ReferencesHandler,
-        './UrlAgent': this.UrlAgent,
-        './ProjectFileAgent': this.ProjectFileAgent,
-        './ProjectOutputFileAgent': this.ProjectOutputFileAgent,
-        '../Editor/EditorController': this.EditorController,
-        '../Project/ProjectLocator': this.ProjectLocator,
-        '@overleaf/logger': this.logger,
-        '@overleaf/settings': this.settings,
-      },
+    this.LinkedFilesController = await esmock.strict(modulePath, {
+      '.../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
+      '../../../../app/src/Features/Analytics/AnalyticsManager':
+        this.AnalyticsManager,
+      '../../../../app/src/Features/LinkedFiles/LinkedFilesHandler':
+        this.LinkedFilesHandler,
+      '../../../../app/src/Features/Editor/EditorRealTimeController':
+        this.EditorRealTimeController,
+      '../../../../app/src/Features/References/ReferencesHandler':
+        this.ReferencesHandler,
+      '../../../../app/src/Features/LinkedFiles/UrlAgent': this.UrlAgent,
+      '../../../../app/src/Features/LinkedFiles/ProjectFileAgent':
+        this.ProjectFileAgent,
+      '../../../../app/src/Features/LinkedFiles/ProjectOutputFileAgent':
+        this.ProjectOutputFileAgent,
+      '../../../../app/src/Features/Editor/EditorController':
+        this.EditorController,
+      '../../../../app/src/Features/Project/ProjectLocator':
+        this.ProjectLocator,
+      '@overleaf/logger': this.logger,
+      '@overleaf/settings': this.settings,
     })
     })
     this.LinkedFilesController._getAgent = sinon.stub().resolves(this.Agent)
     this.LinkedFilesController._getAgent = sinon.stub().resolves(this.Agent)
   })
   })

+ 10 - 11
services/web/test/unit/src/Metadata/MetaControllerTests.js → services/web/test/unit/src/Metadata/MetaControllerTests.mjs

@@ -1,11 +1,11 @@
-const { expect } = require('chai')
-const sinon = require('sinon')
-const modulePath = '../../../../app/src/Features/Metadata/MetaController'
-const SandboxedModule = require('sandboxed-module')
-const MockResponse = require('../helpers/MockResponse')
+import { expect } from 'chai'
+import sinon from 'sinon'
+import esmock from 'esmock'
+import MockResponse from '../helpers/MockResponse.js'
+const modulePath = '../../../../app/src/Features/Metadata/MetaController.mjs'
 
 
 describe('MetaController', function () {
 describe('MetaController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.EditorRealTimeController = {
     this.EditorRealTimeController = {
       emitToRoom: sinon.stub(),
       emitToRoom: sinon.stub(),
     }
     }
@@ -17,11 +17,10 @@ describe('MetaController', function () {
       },
       },
     }
     }
 
 
-    this.MetadataController = SandboxedModule.require(modulePath, {
-      requires: {
-        '../Editor/EditorRealTimeController': this.EditorRealTimeController,
-        './MetaHandler': this.MetaHandler,
-      },
+    this.MetadataController = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Editor/EditorRealTimeController':
+        this.EditorRealTimeController,
+      '../../../../app/src/Features/Metadata/MetaHandler': this.MetaHandler,
     })
     })
   })
   })
 
 

+ 13 - 12
services/web/test/unit/src/Metadata/MetaHandlerTests.js → services/web/test/unit/src/Metadata/MetaHandlerTests.mjs

@@ -1,10 +1,11 @@
-const { expect } = require('chai')
-const sinon = require('sinon')
-const modulePath = '../../../../app/src/Features/Metadata/MetaHandler'
-const SandboxedModule = require('sandboxed-module')
+import { expect } from 'chai'
+import sinon from 'sinon'
+import esmock from 'esmock'
+
+const modulePath = '../../../../app/src/Features/Metadata/MetaHandler.mjs'
 
 
 describe('MetaHandler', function () {
 describe('MetaHandler', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.projectId = 'someprojectid'
     this.projectId = 'someprojectid'
     this.docId = 'somedocid'
     this.docId = 'somedocid'
 
 
@@ -66,13 +67,13 @@ describe('MetaHandler', function () {
       ],
       ],
     }
     }
 
 
-    this.MetaHandler = SandboxedModule.require(modulePath, {
-      requires: {
-        '../Project/ProjectEntityHandler': this.ProjectEntityHandler,
-        '../DocumentUpdater/DocumentUpdaterHandler':
-          this.DocumentUpdaterHandler,
-        './packageMapping': this.packageMapping,
-      },
+    this.MetaHandler = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Project/ProjectEntityHandler':
+        this.ProjectEntityHandler,
+      '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler':
+        this.DocumentUpdaterHandler,
+      '../../../../app/src/Features/Metadata/packageMapping':
+        this.packageMapping,
     })
     })
   })
   })
 
 

+ 13 - 13
services/web/test/unit/src/Notifications/NotificationsControllerTests.js → services/web/test/unit/src/Notifications/NotificationsControllerTests.mjs

@@ -1,15 +1,16 @@
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const modulePath = require('path').join(
-  __dirname,
-  '../../../../app/src/Features/Notifications/NotificationsController.js'
-)
+import esmock from 'esmock'
+import sinon from 'sinon'
+
+const modulePath = new URL(
+  '../../../../app/src/Features/Notifications/NotificationsController.mjs',
+  import.meta.url
+).pathname
 
 
 describe('NotificationsController', function () {
 describe('NotificationsController', function () {
   const userId = '123nd3ijdks'
   const userId = '123nd3ijdks'
   const notificationId = '123njdskj9jlk'
   const notificationId = '123njdskj9jlk'
 
 
-  beforeEach(function () {
+  beforeEach(async function () {
     this.handler = {
     this.handler = {
       getUserNotifications: sinon.stub().callsArgWith(1),
       getUserNotifications: sinon.stub().callsArgWith(1),
       markAsRead: sinon.stub().callsArgWith(2),
       markAsRead: sinon.stub().callsArgWith(2),
@@ -30,12 +31,11 @@ describe('NotificationsController', function () {
     this.AuthenticationController = {
     this.AuthenticationController = {
       getLoggedInUserId: sinon.stub().returns(this.req.session.user._id),
       getLoggedInUserId: sinon.stub().returns(this.req.session.user._id),
     }
     }
-    this.controller = SandboxedModule.require(modulePath, {
-      requires: {
-        './NotificationsHandler': this.handler,
-        '../Authentication/AuthenticationController':
-          this.AuthenticationController,
-      },
+    this.controller = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Notifications/NotificationsHandler':
+        this.handler,
+      '../../../../app/src/Features/Authentication/AuthenticationController':
+        this.AuthenticationController,
     })
     })
   })
   })
 
 

+ 29 - 30
services/web/test/unit/src/PasswordReset/PasswordResetControllerTests.js → services/web/test/unit/src/PasswordReset/PasswordResetControllerTests.mjs

@@ -1,16 +1,15 @@
-const SandboxedModule = require('sandboxed-module')
-const path = require('path')
-const sinon = require('sinon')
-const { expect } = require('chai')
-const MockResponse = require('../helpers/MockResponse')
+import esmock from 'esmock'
+import sinon from 'sinon'
+import { expect } from 'chai'
+import MockResponse from '../helpers/MockResponse.js'
 
 
-const MODULE_PATH = path.join(
-  __dirname,
-  '../../../../app/src/Features/PasswordReset/PasswordResetController'
-)
+const MODULE_PATH = new URL(
+  '../../../../app/src/Features/PasswordReset/PasswordResetController.mjs',
+  import.meta.url
+).pathname
 
 
 describe('PasswordResetController', function () {
 describe('PasswordResetController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.email = 'bob@bob.com'
     this.email = 'bob@bob.com'
     this.user_id = 'mock-user-id'
     this.user_id = 'mock-user-id'
     this.token = 'my security token that was emailed to me'
     this.token = 'my security token that was emailed to me'
@@ -58,27 +57,27 @@ describe('PasswordResetController', function () {
         removeReconfirmFlag: sinon.stub().resolves(),
         removeReconfirmFlag: sinon.stub().resolves(),
       },
       },
     }
     }
-    this.PasswordResetController = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        '@overleaf/settings': this.settings,
-        './PasswordResetHandler': this.PasswordResetHandler,
-        '../Authentication/AuthenticationManager': {
-          validatePassword: sinon.stub().returns(null),
-        },
-        '../Authentication/AuthenticationController':
-          (this.AuthenticationController = {
-            getLoggedInUserId: sinon.stub(),
-            finishLogin: sinon.stub(),
-            setAuditInfo: sinon.stub(),
-          }),
-        '../User/UserGetter': (this.UserGetter = {
-          promises: {
-            getUser: sinon.stub(),
-          },
-        }),
-        '../User/UserSessionsManager': this.UserSessionsManager,
-        '../User/UserUpdater': this.UserUpdater,
+    this.PasswordResetController = await esmock.strict(MODULE_PATH, {
+      '@overleaf/settings': this.settings,
+      '../../../../app/src/Features/PasswordReset/PasswordResetHandler':
+        this.PasswordResetHandler,
+      '../../../../app/src/Features/Authentication/AuthenticationManager': {
+        validatePassword: sinon.stub().returns(null),
       },
       },
+      '../../../../app/src/Features/Authentication/AuthenticationController':
+        (this.AuthenticationController = {
+          getLoggedInUserId: sinon.stub(),
+          finishLogin: sinon.stub(),
+          setAuditInfo: sinon.stub(),
+        }),
+      '../../../../app/src/Features/User/UserGetter': (this.UserGetter = {
+        promises: {
+          getUser: sinon.stub(),
+        },
+      }),
+      '../../../../app/src/Features/User/UserSessionsManager':
+        this.UserSessionsManager,
+      '../../../../app/src/Features/User/UserUpdater': this.UserUpdater,
     })
     })
   })
   })
 
 

+ 20 - 19
services/web/test/unit/src/PasswordReset/PasswordResetHandlerTests.js → services/web/test/unit/src/PasswordReset/PasswordResetHandlerTests.mjs

@@ -1,14 +1,13 @@
-const SandboxedModule = require('sandboxed-module')
-const path = require('path')
-const sinon = require('sinon')
-const { expect } = require('chai')
-const modulePath = path.join(
-  __dirname,
-  '../../../../app/src/Features/PasswordReset/PasswordResetHandler'
-)
+import esmock from 'esmock'
+import sinon from 'sinon'
+import { expect } from 'chai'
+const modulePath = new URL(
+  '../../../../app/src/Features/PasswordReset/PasswordResetHandler',
+  import.meta.url
+).pathname
 
 
 describe('PasswordResetHandler', function () {
 describe('PasswordResetHandler', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.settings = { siteUrl: 'https://www.overleaf.com' }
     this.settings = { siteUrl: 'https://www.overleaf.com' }
     this.OneTimeTokenHandler = {
     this.OneTimeTokenHandler = {
       promises: {
       promises: {
@@ -33,24 +32,26 @@ describe('PasswordResetHandler', function () {
         setUserPassword: sinon.stub().resolves(),
         setUserPassword: sinon.stub().resolves(),
       },
       },
     }
     }
-    this.PasswordResetHandler = SandboxedModule.require(modulePath, {
-      requires: {
-        '../User/UserAuditLogHandler': (this.UserAuditLogHandler = {
+    this.PasswordResetHandler = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/User/UserAuditLogHandler':
+        (this.UserAuditLogHandler = {
           promises: {
           promises: {
             addEntry: sinon.stub().resolves(),
             addEntry: sinon.stub().resolves(),
           },
           },
         }),
         }),
-        '../User/UserGetter': this.UserGetter,
-        '../Security/OneTimeTokenHandler': this.OneTimeTokenHandler,
-        '../Email/EmailHandler': this.EmailHandler,
-        '../Authentication/AuthenticationManager': this.AuthenticationManager,
-        '@overleaf/settings': this.settings,
-        '../Authorization/PermissionsManager': (this.PermissionsManager = {
+      '../../../../app/src/Features/User/UserGetter': this.UserGetter,
+      '../../../../app/src/Features/Security/OneTimeTokenHandler':
+        this.OneTimeTokenHandler,
+      '../../../../app/src/Features/Email/EmailHandler': this.EmailHandler,
+      '../../../../app/src/Features/Authentication/AuthenticationManager':
+        this.AuthenticationManager,
+      '@overleaf/settings': this.settings,
+      '../../../../app/src/Features/Authorization/PermissionsManager':
+        (this.PermissionsManager = {
           promises: {
           promises: {
             assertUserPermissions: sinon.stub(),
             assertUserPermissions: sinon.stub(),
           },
           },
         }),
         }),
-      },
     })
     })
     this.token = '12312321i'
     this.token = '12312321i'
     this.user_id = 'user_id_here'
     this.user_id = 'user_id_here'

+ 27 - 39
services/web/test/unit/src/Project/DocLinesComparitorTests.js → services/web/test/unit/src/Project/DocLinesComparitorTests.mjs

@@ -1,92 +1,80 @@
-/* eslint-disable
-    max-len,
-    mocha/no-identical-title,
-    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
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-const sinon = require('sinon')
-const modulePath = '../../../../app/src/Features/Project/DocLinesComparitor.js'
-const SandboxedModule = require('sandboxed-module')
+import esmock from 'esmock'
+
+const modulePath = '../../../../app/src/Features/Project/DocLinesComparitor.mjs'
 
 
 describe('doc lines comparitor', function () {
 describe('doc lines comparitor', function () {
-  beforeEach(function () {
-    return (this.comparitor = SandboxedModule.require(modulePath, {}))
+  beforeEach(async function () {
+    this.comparitor = await esmock.strict(modulePath, {})
   })
   })
 
 
   it('should return true when the lines are the same', function () {
   it('should return true when the lines are the same', function () {
     const lines1 = ['hello', 'world']
     const lines1 = ['hello', 'world']
     const lines2 = ['hello', 'world']
     const lines2 = ['hello', 'world']
     const result = this.comparitor.areSame(lines1, lines2)
     const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(true)
-  })
-
-  it('should return false when the lines are different', function () {
-    const lines1 = ['hello', 'world']
-    const lines2 = ['diff', 'world']
-    const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(false)
+    result.should.equal(true)
   })
   })
-
-  it('should return false when the lines are different', function () {
-    const lines1 = ['hello', 'world']
-    const lines2 = ['hello', 'wrld']
-    const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(false)
+  ;[
+    {
+      lines1: ['hello', 'world'],
+      lines2: ['diff', 'world'],
+    },
+    {
+      lines1: ['hello', 'world'],
+      lines2: ['hello', 'wrld'],
+    },
+  ].forEach(({ lines1, lines2 }) => {
+    it('should return false when the lines are different', function () {
+      const result = this.comparitor.areSame(lines1, lines2)
+      result.should.equal(false)
+    })
   })
   })
 
 
   it('should return true when the lines are same', function () {
   it('should return true when the lines are same', function () {
     const lines1 = ['hello', 'world']
     const lines1 = ['hello', 'world']
     const lines2 = ['hello', 'world']
     const lines2 = ['hello', 'world']
     const result = this.comparitor.areSame(lines1, lines2)
     const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(true)
+    result.should.equal(true)
   })
   })
 
 
   it('should return false if the doc lines are different in length', function () {
   it('should return false if the doc lines are different in length', function () {
     const lines1 = ['hello', 'world']
     const lines1 = ['hello', 'world']
     const lines2 = ['hello', 'world', 'please']
     const lines2 = ['hello', 'world', 'please']
     const result = this.comparitor.areSame(lines1, lines2)
     const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(false)
+    result.should.equal(false)
   })
   })
 
 
   it('should return false if the first array is undefined', function () {
   it('should return false if the first array is undefined', function () {
     const lines1 = undefined
     const lines1 = undefined
     const lines2 = ['hello', 'world']
     const lines2 = ['hello', 'world']
     const result = this.comparitor.areSame(lines1, lines2)
     const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(false)
+    result.should.equal(false)
   })
   })
 
 
   it('should return false if the second array is undefined', function () {
   it('should return false if the second array is undefined', function () {
     const lines1 = ['hello']
     const lines1 = ['hello']
     const lines2 = undefined
     const lines2 = undefined
     const result = this.comparitor.areSame(lines1, lines2)
     const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(false)
+    result.should.equal(false)
   })
   })
 
 
   it('should return false if the second array is not an array', function () {
   it('should return false if the second array is not an array', function () {
     const lines1 = ['hello']
     const lines1 = ['hello']
     const lines2 = ''
     const lines2 = ''
     const result = this.comparitor.areSame(lines1, lines2)
     const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(false)
+    result.should.equal(false)
   })
   })
 
 
   it('should return true when comparing equal orchard docs', function () {
   it('should return true when comparing equal orchard docs', function () {
     const lines1 = [{ text: 'hello world' }]
     const lines1 = [{ text: 'hello world' }]
     const lines2 = [{ text: 'hello world' }]
     const lines2 = [{ text: 'hello world' }]
     const result = this.comparitor.areSame(lines1, lines2)
     const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(true)
+    result.should.equal(true)
   })
   })
 
 
   it('should return false when comparing different orchard docs', function () {
   it('should return false when comparing different orchard docs', function () {
     const lines1 = [{ text: 'goodbye world' }]
     const lines1 = [{ text: 'goodbye world' }]
     const lines2 = [{ text: 'hello world' }]
     const lines2 = [{ text: 'hello world' }]
     const result = this.comparitor.areSame(lines1, lines2)
     const result = this.comparitor.areSame(lines1, lines2)
-    return result.should.equal(false)
+    result.should.equal(false)
   })
   })
 })
 })

+ 7 - 12
services/web/test/unit/src/Project/ProjectApiControllerTests.js → services/web/test/unit/src/Project/ProjectApiControllerTests.mjs

@@ -1,8 +1,3 @@
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
 // TODO: This file was created by bulk-decaffeinate.
 // TODO: This file was created by bulk-decaffeinate.
 // Fix any style issues and re-enable lint.
 // Fix any style issues and re-enable lint.
 /*
 /*
@@ -10,17 +5,17 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * DS102: Remove unnecessary code created because of implicit returns
  * 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 esmock from 'esmock'
+import sinon from 'sinon'
+
 const modulePath = '../../../../app/src/Features/Project/ProjectApiController'
 const modulePath = '../../../../app/src/Features/Project/ProjectApiController'
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
 
 
 describe('Project api controller', function () {
 describe('Project api controller', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.ProjectDetailsHandler = { getDetails: sinon.stub() }
     this.ProjectDetailsHandler = { getDetails: sinon.stub() }
-    this.controller = SandboxedModule.require(modulePath, {
-      requires: {
-        './ProjectDetailsHandler': this.ProjectDetailsHandler,
-      },
+    this.controller = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Project/ProjectDetailsHandler':
+        this.ProjectDetailsHandler,
     })
     })
     this.project_id = '321l3j1kjkjl'
     this.project_id = '321l3j1kjkjl'
     this.req = {
     this.req = {

+ 47 - 40
services/web/test/unit/src/Project/ProjectListControllerTests.js → services/web/test/unit/src/Project/ProjectListControllerTests.mjs

@@ -1,17 +1,18 @@
-const SandboxedModule = require('sandboxed-module')
-const path = require('path')
-const sinon = require('sinon')
-const { expect } = require('chai')
-const { ObjectId } = require('mongodb-legacy')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
-
-const MODULE_PATH = path.join(
-  __dirname,
-  '../../../../app/src/Features/Project/ProjectListController'
-)
+import esmock from 'esmock'
+import sinon from 'sinon'
+import { expect } from 'chai'
+import mongodb from 'mongodb-legacy'
+import Errors from '../../../../app/src/Features/Errors/Errors.js'
+
+const ObjectId = mongodb.ObjectId
+
+const MODULE_PATH = new URL(
+  '../../../../app/src/Features/Project/ProjectListController',
+  import.meta.url
+).pathname
 
 
 describe('ProjectListController', function () {
 describe('ProjectListController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.project_id = new ObjectId('abcdefabcdefabcdefabcdef')
     this.project_id = new ObjectId('abcdefabcdefabcdefabcdef')
 
 
     this.user = {
     this.user = {
@@ -150,34 +151,40 @@ describe('ProjectListController', function () {
       },
       },
     }
     }
 
 
-    this.ProjectListController = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        'mongodb-legacy': { ObjectId },
-        '@overleaf/settings': this.settings,
-        '@overleaf/metrics': this.Metrics,
-        '../SplitTests/SplitTestHandler': this.SplitTestHandler,
-        '../SplitTests/SplitTestSessionHandler': this.SplitTestSessionHandler,
-        '../User/UserController': this.UserController,
-        './ProjectHelper': this.ProjectHelper,
-        '../Subscription/LimitationsManager': this.LimitationsManager,
-        '../Tags/TagsHandler': this.TagsHandler,
-        '../Notifications/NotificationsHandler': this.NotificationsHandler,
-        '../../models/User': { User: this.UserModel },
-        './ProjectGetter': this.ProjectGetter,
-        '../Authentication/SessionManager': this.SessionManager,
-        '../../infrastructure/Features': this.Features,
-        '../User/UserGetter': this.UserGetter,
-        '../Subscription/SubscriptionViewModelBuilder':
-          this.SubscriptionViewModelBuilder,
-        '../../infrastructure/Modules': this.Modules,
-        '../Survey/SurveyHandler': this.SurveyHandler,
-        '../User/UserPrimaryEmailCheckHandler':
-          this.UserPrimaryEmailCheckHandler,
-        '../Notifications/NotificationsBuilder': this.NotificationBuilder,
-        '../Subscription/SubscriptionLocator': this.SubscriptionLocator,
-        '../../infrastructure/GeoIpLookup': this.GeoIpLookup,
-        '../Tutorial/TutorialHandler': this.TutorialHandler,
-      },
+    this.ProjectListController = await esmock.strict(MODULE_PATH, {
+      'mongodb-legacy': { ObjectId },
+      '@overleaf/settings': this.settings,
+      '@overleaf/metrics': this.Metrics,
+      '../../../../app/src/Features/SplitTests/SplitTestHandler':
+        this.SplitTestHandler,
+      '../../../../app/src/Features/SplitTests/SplitTestSessionHandler':
+        this.SplitTestSessionHandler,
+      '../../../../app/src/Features/User/UserController': this.UserController,
+      '../../../../app/src/Features/Project/ProjectHelper': this.ProjectHelper,
+      '../../../../app/src/Features/Subscription/LimitationsManager':
+        this.LimitationsManager,
+      '../../../../app/src/Features/Tags/TagsHandler': this.TagsHandler,
+      '../../../../app/src/Features/Notifications/NotificationsHandler':
+        this.NotificationsHandler,
+      '../../../../app/src/models/User': { User: this.UserModel },
+      '../../../../app/src/Features/Project/ProjectGetter': this.ProjectGetter,
+      '../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
+      '../../../../app/src/infrastructure/Features': this.Features,
+      '../../../../app/src/Features/User/UserGetter': this.UserGetter,
+      '../../../../app/src/Features/Subscription/SubscriptionViewModelBuilder':
+        this.SubscriptionViewModelBuilder,
+      '../../../../app/src/infrastructure/Modules': this.Modules,
+      '../../../../app/src/Features/Survey/SurveyHandler': this.SurveyHandler,
+      '../../../../app/src/Features/User/UserPrimaryEmailCheckHandler':
+        this.UserPrimaryEmailCheckHandler,
+      '../../../../app/src/Features/Notifications/NotificationsBuilder':
+        this.NotificationBuilder,
+      '../../../../app/src/Features/Subscription/SubscriptionLocator':
+        this.SubscriptionLocator,
+      '../../../../app/src/infrastructure/GeoIpLookup': this.GeoIpLookup,
+      '../../../../app/src/Features/Tutorial/TutorialHandler':
+        this.TutorialHandler,
     })
     })
 
 
     this.req = {
     this.req = {

+ 7 - 7
services/web/test/unit/src/Referal/ReferalConnectTests.js → services/web/test/unit/src/Referal/ReferalConnectTests.mjs

@@ -1,12 +1,12 @@
-const SandboxedModule = require('sandboxed-module')
-const modulePath = require('path').join(
-  __dirname,
-  '../../../../app/src/Features/Referal/ReferalConnect.js'
-)
+import esmock from 'esmock'
+const modulePath = new URL(
+  '../../../../app/src/Features/Referal/ReferalConnect.mjs',
+  import.meta.url
+).pathname
 
 
 describe('Referal connect middle wear', function () {
 describe('Referal connect middle wear', function () {
-  beforeEach(function () {
-    this.connect = SandboxedModule.require(modulePath, {})
+  beforeEach(async function () {
+    this.connect = await esmock.strict(modulePath, {})
   })
   })
 
 
   it('should take a referal query string and put it on the session if it exists', function (done) {
   it('should take a referal query string and put it on the session if it exists', function (done) {

+ 0 - 11
services/web/test/unit/src/Referal/ReferalControllerTests.js

@@ -1,11 +0,0 @@
-const SandboxedModule = require('sandboxed-module')
-const modulePath = require('path').join(
-  __dirname,
-  '../../../../app/src/Features/Referal/ReferalController.js'
-)
-
-describe('Referal controller', function () {
-  beforeEach(function () {
-    this.controller = SandboxedModule.require(modulePath, {})
-  })
-})

+ 11 - 0
services/web/test/unit/src/Referal/ReferalControllerTests.mjs

@@ -0,0 +1,11 @@
+import esmock from 'esmock'
+const modulePath = new URL(
+  '../../../../app/src/Features/Referal/ReferalController.js',
+  import.meta.url
+).pathname
+
+describe('Referal controller', function () {
+  beforeEach(async function () {
+    this.controller = await esmock.strict(modulePath, {})
+  })
+})

+ 11 - 13
services/web/test/unit/src/Referal/ReferalHandlerTests.js → services/web/test/unit/src/Referal/ReferalHandlerTests.mjs

@@ -1,23 +1,21 @@
-const SandboxedModule = require('sandboxed-module')
-const { expect } = require('chai')
-const sinon = require('sinon')
-const modulePath = require('path').join(
-  __dirname,
-  '../../../../app/src/Features/Referal/ReferalHandler.js'
-)
+import esmock from 'esmock'
+import { expect } from 'chai'
+import sinon from 'sinon'
+const modulePath = new URL(
+  '../../../../app/src/Features/Referal/ReferalHandler.mjs',
+  import.meta.url
+).pathname
 
 
 describe('Referal handler', function () {
 describe('Referal handler', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.User = {
     this.User = {
       findById: sinon.stub().returns({
       findById: sinon.stub().returns({
         exec: sinon.stub(),
         exec: sinon.stub(),
       }),
       }),
     }
     }
-    this.handler = SandboxedModule.require(modulePath, {
-      requires: {
-        '../../models/User': {
-          User: this.User,
-        },
+    this.handler = await esmock.strict(modulePath, {
+      '../../../../app/src/models/User': {
+        User: this.User,
       },
       },
     })
     })
     this.user_id = '12313'
     this.user_id = '12313'

+ 46 - 59
services/web/test/unit/src/References/ReferencesControllerTests.js → services/web/test/unit/src/References/ReferencesControllerTests.mjs

@@ -1,39 +1,26 @@
-/* eslint-disable
-    max-len,
-    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
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const { assert } = require('chai')
+import esmock from 'esmock'
+import sinon from 'sinon'
+import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.js'
 const modulePath =
 const modulePath =
   '../../../../app/src/Features/References/ReferencesController'
   '../../../../app/src/Features/References/ReferencesController'
-const MockRequest = require('../helpers/MockRequest')
-const MockResponse = require('../helpers/MockResponse')
 
 
 describe('ReferencesController', function () {
 describe('ReferencesController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.projectId = '2222'
     this.projectId = '2222'
-    this.controller = SandboxedModule.require(modulePath, {
-      requires: {
-        '@overleaf/settings': (this.settings = {
-          apis: { web: { url: 'http://some.url' } },
-        }),
-        './ReferencesHandler': (this.ReferencesHandler = {
+    this.controller = await esmock.strict(modulePath, {
+      '@overleaf/settings': (this.settings = {
+        apis: { web: { url: 'http://some.url' } },
+      }),
+      '../../../../app/src/Features/References/ReferencesHandler':
+        (this.ReferencesHandler = {
           index: sinon.stub(),
           index: sinon.stub(),
           indexAll: sinon.stub(),
           indexAll: sinon.stub(),
         }),
         }),
-        '../Editor/EditorRealTimeController': (this.EditorRealTimeController = {
+      '../../../../app/src/Features/Editor/EditorRealTimeController':
+        (this.EditorRealTimeController = {
           emitToRoom: sinon.stub(),
           emitToRoom: sinon.stub(),
         }),
         }),
-      },
     })
     })
     this.req = new MockRequest()
     this.req = new MockRequest()
     this.req.params.Project_id = this.projectId
     this.req.params.Project_id = this.projectId
@@ -45,10 +32,10 @@ describe('ReferencesController', function () {
     this.res.json = sinon.stub()
     this.res.json = sinon.stub()
     this.res.sendStatus = sinon.stub()
     this.res.sendStatus = sinon.stub()
     this.next = sinon.stub()
     this.next = sinon.stub()
-    return (this.fakeResponseData = {
+    this.fakeResponseData = {
       projectId: this.projectId,
       projectId: this.projectId,
       keys: ['one', 'two', 'three'],
       keys: ['one', 'two', 'three'],
-    })
+    }
   })
   })
 
 
   describe('indexAll', function () {
   describe('indexAll', function () {
@@ -59,36 +46,36 @@ describe('ReferencesController', function () {
         null,
         null,
         this.fakeResponseData
         this.fakeResponseData
       )
       )
-      return (this.call = callback => {
+      this.call = callback => {
         this.controller.indexAll(this.req, this.res, this.next)
         this.controller.indexAll(this.req, this.res, this.next)
         return callback()
         return callback()
-      })
+      }
     })
     })
 
 
     it('should not produce an error', function (done) {
     it('should not produce an error', function (done) {
-      return this.call(() => {
+      this.call(() => {
         this.res.sendStatus.callCount.should.equal(0)
         this.res.sendStatus.callCount.should.equal(0)
         this.res.sendStatus.calledWith(500).should.equal(false)
         this.res.sendStatus.calledWith(500).should.equal(false)
         this.res.sendStatus.calledWith(400).should.equal(false)
         this.res.sendStatus.calledWith(400).should.equal(false)
-        return done()
+        done()
       })
       })
     })
     })
 
 
     it('should return data', function (done) {
     it('should return data', function (done) {
-      return this.call(() => {
+      this.call(() => {
         this.res.json.callCount.should.equal(1)
         this.res.json.callCount.should.equal(1)
         this.res.json.calledWith(this.fakeResponseData).should.equal(true)
         this.res.json.calledWith(this.fakeResponseData).should.equal(true)
-        return done()
+        done()
       })
       })
     })
     })
 
 
     it('should call ReferencesHandler.indexAll', function (done) {
     it('should call ReferencesHandler.indexAll', function (done) {
-      return this.call(() => {
+      this.call(() => {
         this.ReferencesHandler.indexAll.callCount.should.equal(1)
         this.ReferencesHandler.indexAll.callCount.should.equal(1)
         this.ReferencesHandler.indexAll
         this.ReferencesHandler.indexAll
           .calledWith(this.projectId)
           .calledWith(this.projectId)
           .should.equal(true)
           .should.equal(true)
-        return done()
+        done()
       })
       })
     })
     })
 
 
@@ -99,30 +86,30 @@ describe('ReferencesController', function () {
           null,
           null,
           this.fakeResponseData
           this.fakeResponseData
         )
         )
-        return (this.req.body.shouldBroadcast = true)
+        this.req.body.shouldBroadcast = true
       })
       })
 
 
       it('should call EditorRealTimeController.emitToRoom', function (done) {
       it('should call EditorRealTimeController.emitToRoom', function (done) {
-        return this.call(() => {
+        this.call(() => {
           this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
           this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
-          return done()
+          done()
         })
         })
       })
       })
 
 
       it('should not produce an error', function (done) {
       it('should not produce an error', function (done) {
-        return this.call(() => {
+        this.call(() => {
           this.res.sendStatus.callCount.should.equal(0)
           this.res.sendStatus.callCount.should.equal(0)
           this.res.sendStatus.calledWith(500).should.equal(false)
           this.res.sendStatus.calledWith(500).should.equal(false)
           this.res.sendStatus.calledWith(400).should.equal(false)
           this.res.sendStatus.calledWith(400).should.equal(false)
-          return done()
+          done()
         })
         })
       })
       })
 
 
       it('should still return data', function (done) {
       it('should still return data', function (done) {
-        return this.call(() => {
+        this.call(() => {
           this.res.json.callCount.should.equal(1)
           this.res.json.callCount.should.equal(1)
           this.res.json.calledWith(this.fakeResponseData).should.equal(true)
           this.res.json.calledWith(this.fakeResponseData).should.equal(true)
-          return done()
+          done()
         })
         })
       })
       })
     })
     })
@@ -134,30 +121,30 @@ describe('ReferencesController', function () {
           null,
           null,
           this.fakeResponseData
           this.fakeResponseData
         )
         )
-        return (this.req.body.shouldBroadcast = false)
+        this.req.body.shouldBroadcast = false
       })
       })
 
 
       it('should not call EditorRealTimeController.emitToRoom', function (done) {
       it('should not call EditorRealTimeController.emitToRoom', function (done) {
-        return this.call(() => {
+        this.call(() => {
           this.EditorRealTimeController.emitToRoom.callCount.should.equal(0)
           this.EditorRealTimeController.emitToRoom.callCount.should.equal(0)
-          return done()
+          done()
         })
         })
       })
       })
 
 
       it('should not produce an error', function (done) {
       it('should not produce an error', function (done) {
-        return this.call(() => {
+        this.call(() => {
           this.res.sendStatus.callCount.should.equal(0)
           this.res.sendStatus.callCount.should.equal(0)
           this.res.sendStatus.calledWith(500).should.equal(false)
           this.res.sendStatus.calledWith(500).should.equal(false)
           this.res.sendStatus.calledWith(400).should.equal(false)
           this.res.sendStatus.calledWith(400).should.equal(false)
-          return done()
+          done()
         })
         })
       })
       })
 
 
       it('should still return data', function (done) {
       it('should still return data', function (done) {
-        return this.call(() => {
+        this.call(() => {
           this.res.json.callCount.should.equal(1)
           this.res.json.callCount.should.equal(1)
           this.res.json.calledWith(this.fakeResponseData).should.equal(true)
           this.res.json.calledWith(this.fakeResponseData).should.equal(true)
-          return done()
+          done()
         })
         })
       })
       })
     })
     })
@@ -166,35 +153,35 @@ describe('ReferencesController', function () {
   describe('there is no data', function () {
   describe('there is no data', function () {
     beforeEach(function () {
     beforeEach(function () {
       this.ReferencesHandler.indexAll.callsArgWith(1)
       this.ReferencesHandler.indexAll.callsArgWith(1)
-      return (this.call = callback => {
+      this.call = callback => {
         this.controller.indexAll(this.req, this.res, this.next)
         this.controller.indexAll(this.req, this.res, this.next)
-        return callback()
-      })
+        callback()
+      }
     })
     })
 
 
     it('should not call EditorRealTimeController.emitToRoom', function (done) {
     it('should not call EditorRealTimeController.emitToRoom', function (done) {
-      return this.call(() => {
+      this.call(() => {
         this.EditorRealTimeController.emitToRoom.callCount.should.equal(0)
         this.EditorRealTimeController.emitToRoom.callCount.should.equal(0)
-        return done()
+        done()
       })
       })
     })
     })
 
 
     it('should not produce an error', function (done) {
     it('should not produce an error', function (done) {
-      return this.call(() => {
+      this.call(() => {
         this.res.sendStatus.callCount.should.equal(0)
         this.res.sendStatus.callCount.should.equal(0)
         this.res.sendStatus.calledWith(500).should.equal(false)
         this.res.sendStatus.calledWith(500).should.equal(false)
         this.res.sendStatus.calledWith(400).should.equal(false)
         this.res.sendStatus.calledWith(400).should.equal(false)
-        return done()
+        done()
       })
       })
     })
     })
 
 
     it('should send a response with an empty keys list', function (done) {
     it('should send a response with an empty keys list', function (done) {
-      return this.call(() => {
+      this.call(() => {
         this.res.json.called.should.equal(true)
         this.res.json.called.should.equal(true)
         this.res.json
         this.res.json
           .calledWith({ projectId: this.projectId, keys: [] })
           .calledWith({ projectId: this.projectId, keys: [] })
           .should.equal(true)
           .should.equal(true)
-        return done()
+        done()
       })
       })
     })
     })
   })
   })

+ 42 - 43
services/web/test/unit/src/References/ReferencesHandlerTests.js → services/web/test/unit/src/References/ReferencesHandlerTests.mjs

@@ -1,10 +1,3 @@
-/* eslint-disable
-    n/handle-callback-err,
-    max-len,
-    mocha/no-identical-title,
-    no-return-assign,
-    no-unused-vars,
-*/
 // TODO: This file was created by bulk-decaffeinate.
 // TODO: This file was created by bulk-decaffeinate.
 // Fix any style issues and re-enable lint.
 // Fix any style issues and re-enable lint.
 /*
 /*
@@ -12,14 +5,16 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
-const SandboxedModule = require('sandboxed-module')
-const { assert, expect } = require('chai')
-const sinon = require('sinon')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
-const modulePath = '../../../../app/src/Features/References/ReferencesHandler'
+import esmock from 'esmock'
+
+import { expect } from 'chai'
+import sinon from 'sinon'
+import Errors from '../../../../app/src/Features/Errors/Errors.js'
+const modulePath =
+  '../../../../app/src/Features/References/ReferencesHandler.mjs'
 
 
 describe('ReferencesHandler', function () {
 describe('ReferencesHandler', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.projectId = '222'
     this.projectId = '222'
     this.fakeProject = {
     this.fakeProject = {
       _id: this.projectId,
       _id: this.projectId,
@@ -46,38 +41,37 @@ describe('ReferencesHandler', function () {
       ],
       ],
     }
     }
     this.docIds = ['aaa', 'ccc']
     this.docIds = ['aaa', 'ccc']
-    this.handler = SandboxedModule.require(modulePath, {
-      requires: {
-        '@overleaf/settings': (this.settings = {
-          apis: {
-            references: { url: 'http://some.url/references' },
-            docstore: { url: 'http://some.url/docstore' },
-            filestore: { url: 'http://some.url/filestore' },
-          },
-        }),
-        request: (this.request = {
-          get: sinon.stub(),
-          post: sinon.stub(),
-        }),
-        '../Project/ProjectGetter': (this.ProjectGetter = {
+    this.handler = await esmock.strict(modulePath, {
+      '@overleaf/settings': (this.settings = {
+        apis: {
+          references: { url: 'http://some.url/references' },
+          docstore: { url: 'http://some.url/docstore' },
+          filestore: { url: 'http://some.url/filestore' },
+        },
+      }),
+      request: (this.request = {
+        get: sinon.stub(),
+        post: sinon.stub(),
+      }),
+      '../../../../app/src/Features/Project/ProjectGetter':
+        (this.ProjectGetter = {
           getProject: sinon.stub().callsArgWith(2, null, this.fakeProject),
           getProject: sinon.stub().callsArgWith(2, null, this.fakeProject),
         }),
         }),
-        '../User/UserGetter': (this.UserGetter = {
-          getUser: sinon.stub(),
-        }),
-        '../DocumentUpdater/DocumentUpdaterHandler':
-          (this.DocumentUpdaterHandler = {
-            flushDocToMongo: sinon.stub().callsArgWith(2, null),
-          }),
-        '../../infrastructure/Features': (this.Features = {
-          hasFeature: sinon.stub().returns(true),
+      '../../../../app/src/Features/User/UserGetter': (this.UserGetter = {
+        getUser: sinon.stub(),
+      }),
+      '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler':
+        (this.DocumentUpdaterHandler = {
+          flushDocToMongo: sinon.stub().callsArgWith(2, null),
         }),
         }),
-      },
+      '../../../../app/src/infrastructure/Features': (this.Features = {
+        hasFeature: sinon.stub().returns(true),
+      }),
     })
     })
-    return (this.fakeResponseData = {
+    this.fakeResponseData = {
       projectId: this.projectId,
       projectId: this.projectId,
       keys: ['k1', 'k2'],
       keys: ['k1', 'k2'],
-    })
+    }
   })
   })
 
 
   describe('indexAll', function () {
   describe('indexAll', function () {
@@ -98,6 +92,7 @@ describe('ReferencesHandler', function () {
 
 
     it('should call _findBibDocIds', function (done) {
     it('should call _findBibDocIds', function (done) {
       return this.call((err, data) => {
       return this.call((err, data) => {
+        expect(err).to.be.null
         this.handler._findBibDocIds.callCount.should.equal(1)
         this.handler._findBibDocIds.callCount.should.equal(1)
         this.handler._findBibDocIds
         this.handler._findBibDocIds
           .calledWith(this.fakeProject)
           .calledWith(this.fakeProject)
@@ -108,6 +103,7 @@ describe('ReferencesHandler', function () {
 
 
     it('should call _findBibFileIds', function (done) {
     it('should call _findBibFileIds', function (done) {
       return this.call((err, data) => {
       return this.call((err, data) => {
+        expect(err).to.be.null
         this.handler._findBibDocIds.callCount.should.equal(1)
         this.handler._findBibDocIds.callCount.should.equal(1)
         this.handler._findBibDocIds
         this.handler._findBibDocIds
           .calledWith(this.fakeProject)
           .calledWith(this.fakeProject)
@@ -118,6 +114,7 @@ describe('ReferencesHandler', function () {
 
 
     it('should call DocumentUpdaterHandler.flushDocToMongo', function (done) {
     it('should call DocumentUpdaterHandler.flushDocToMongo', function (done) {
       return this.call((err, data) => {
       return this.call((err, data) => {
+        expect(err).to.be.null
         this.DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal(2)
         this.DocumentUpdaterHandler.flushDocToMongo.callCount.should.equal(2)
         return done()
         return done()
       })
       })
@@ -125,6 +122,7 @@ describe('ReferencesHandler', function () {
 
 
     it('should make a request to references service', function (done) {
     it('should make a request to references service', function (done) {
       return this.call((err, data) => {
       return this.call((err, data) => {
+        expect(err).to.be.null
         this.request.post.callCount.should.equal(1)
         this.request.post.callCount.should.equal(1)
         const arg = this.request.post.firstCall.args[0]
         const arg = this.request.post.firstCall.args[0]
         expect(arg.json).to.have.all.keys('docUrls', 'fullIndex')
         expect(arg.json).to.have.all.keys('docUrls', 'fullIndex')
@@ -143,6 +141,7 @@ describe('ReferencesHandler', function () {
 
 
     it('should return data', function (done) {
     it('should return data', function (done) {
       return this.call((err, data) => {
       return this.call((err, data) => {
+        expect(err).to.be.null
         expect(data).to.not.equal(null)
         expect(data).to.not.equal(null)
         expect(data).to.not.equal(undefined)
         expect(data).to.not.equal(undefined)
         expect(data).to.equal(this.fakeResponseData)
         expect(data).to.equal(this.fakeResponseData)
@@ -165,7 +164,7 @@ describe('ReferencesHandler', function () {
       })
       })
 
 
       it('should not send request', function (done) {
       it('should not send request', function (done) {
-        return this.call((err, data) => {
+        return this.call(() => {
           this.request.post.callCount.should.equal(0)
           this.request.post.callCount.should.equal(0)
           return done()
           return done()
         })
         })
@@ -187,7 +186,7 @@ describe('ReferencesHandler', function () {
       })
       })
 
 
       it('should not send request', function (done) {
       it('should not send request', function (done) {
-        return this.call((err, data) => {
+        return this.call(() => {
           this.request.post.callCount.should.equal(0)
           this.request.post.callCount.should.equal(0)
           return done()
           return done()
         })
         })
@@ -210,7 +209,7 @@ describe('ReferencesHandler', function () {
       })
       })
 
 
       it('should not send request', function (done) {
       it('should not send request', function (done) {
-        return this.call((err, data) => {
+        return this.call(() => {
           this.request.post.callCount.should.equal(0)
           this.request.post.callCount.should.equal(0)
           return done()
           return done()
         })
         })
@@ -237,7 +236,7 @@ describe('ReferencesHandler', function () {
       })
       })
 
 
       it('should not send request', function (done) {
       it('should not send request', function (done) {
-        return this.call((err, data) => {
+        return this.call(() => {
           this.request.post.callCount.should.equal(0)
           this.request.post.callCount.should.equal(0)
           return done()
           return done()
         })
         })

+ 19 - 21
services/web/test/unit/src/Spelling/SpellingControllerTests.js → services/web/test/unit/src/Spelling/SpellingControllerTests.mjs

@@ -1,10 +1,10 @@
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const MockResponse = require('../helpers/MockResponse')
-const modulePath = require('path').join(
-  __dirname,
-  '../../../../app/src/Features/Spelling/SpellingController.js'
-)
+import esmock from 'esmock'
+import sinon from 'sinon'
+import MockResponse from '../helpers/MockResponse.js'
+const modulePath = new URL(
+  '../../../../app/src/Features/Spelling/SpellingController.mjs',
+  import.meta.url
+).pathname
 
 
 const TEN_SECONDS = 1000 * 10
 const TEN_SECONDS = 1000 * 10
 
 
@@ -14,7 +14,7 @@ const SPELLING_URL = 'http://spelling.service.test'
 describe('SpellingController', function () {
 describe('SpellingController', function () {
   const userId = '123nd3ijdks'
   const userId = '123nd3ijdks'
 
 
-  beforeEach(function () {
+  beforeEach(async function () {
     this.requestStreamPipe = sinon.stub()
     this.requestStreamPipe = sinon.stub()
     this.requestStreamOn = sinon
     this.requestStreamOn = sinon
       .stub()
       .stub()
@@ -26,20 +26,18 @@ describe('SpellingController', function () {
     this.AuthenticationController = {
     this.AuthenticationController = {
       getLoggedInUserId: req => req.session.user._id,
       getLoggedInUserId: req => req.session.user._id,
     }
     }
-    this.controller = SandboxedModule.require(modulePath, {
-      requires: {
-        './LearnedWordsManager': {},
-        request: this.request,
-        '@overleaf/settings': {
-          languages: [
-            { name: 'English', code: 'en' },
-            { name: 'French', code: 'fr' },
-          ],
-          apis: { spelling: { host: SPELLING_HOST, url: SPELLING_URL } },
-        },
-        '../Authentication/AuthenticationController':
-          this.AuthenticationController,
+    this.controller = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Spelling/LearnedWordsManager': {},
+      request: this.request,
+      '@overleaf/settings': {
+        languages: [
+          { name: 'English', code: 'en' },
+          { name: 'French', code: 'fr' },
+        ],
+        apis: { spelling: { host: SPELLING_HOST, url: SPELLING_URL } },
       },
       },
+      '../../../../app/src/Features/Authentication/AuthenticationController':
+        this.AuthenticationController,
     })
     })
     this.req = {
     this.req = {
       url: '/spelling/check',
       url: '/spelling/check',

+ 13 - 11
services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.js → services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs

@@ -1,10 +1,10 @@
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
+import esmock from 'esmock'
+import sinon from 'sinon'
 const modulePath =
 const modulePath =
   '../../../../app/src/Features/Subscription/SubscriptionGroupController'
   '../../../../app/src/Features/Subscription/SubscriptionGroupController'
 
 
 describe('SubscriptionGroupController', function () {
 describe('SubscriptionGroupController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.user = { _id: '!@312431', email: 'user@email.com' }
     this.user = { _id: '!@312431', email: 'user@email.com' }
     this.adminUserId = '123jlkj'
     this.adminUserId = '123jlkj'
     this.subscriptionId = '123434325412'
     this.subscriptionId = '123434325412'
@@ -61,14 +61,16 @@ describe('SubscriptionGroupController', function () {
       },
       },
     }
     }
 
 
-    this.Controller = SandboxedModule.require(modulePath, {
-      requires: {
-        './SubscriptionGroupHandler': this.SubscriptionGroupHandler,
-        './SubscriptionLocator': this.SubscriptionLocator,
-        '../Authentication/SessionManager': this.SessionManager,
-        '../User/UserAuditLogHandler': this.UserAuditLogHandler,
-        '../../infrastructure/Modules': this.Modules,
-      },
+    this.Controller = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Subscription/SubscriptionGroupHandler':
+        this.SubscriptionGroupHandler,
+      '../../../../app/src/Features/Subscription/SubscriptionLocator':
+        this.SubscriptionLocator,
+      '../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
+      '../../../../app/src/Features/User/UserAuditLogHandler':
+        this.UserAuditLogHandler,
+      '../../../../app/src/infrastructure/Modules': this.Modules,
     })
     })
   })
   })
 
 

+ 26 - 22
services/web/test/unit/src/Subscription/TeamInvitesControllerTests.js → services/web/test/unit/src/Subscription/TeamInvitesControllerTests.mjs

@@ -1,11 +1,11 @@
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const { expect } = require('chai')
+import esmock from 'esmock'
+import sinon from 'sinon'
+import { expect } from 'chai'
 const modulePath =
 const modulePath =
   '../../../../app/src/Features/Subscription/TeamInvitesController'
   '../../../../app/src/Features/Subscription/TeamInvitesController'
 
 
 describe('TeamInvitesController', function () {
 describe('TeamInvitesController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.user = { _id: '!@312431', email: 'user@email.com' }
     this.user = { _id: '!@312431', email: 'user@email.com' }
     this.adminUserId = '123jlkj'
     this.adminUserId = '123jlkj'
     this.subscriptionId = '123434325412'
     this.subscriptionId = '123434325412'
@@ -77,29 +77,33 @@ describe('TeamInvitesController', function () {
       RateLimiter: class {},
       RateLimiter: class {},
     }
     }
 
 
-    this.Controller = SandboxedModule.require(modulePath, {
-      requires: {
-        './TeamInvitesHandler': this.TeamInvitesHandler,
-        '../Authentication/SessionManager': this.SessionManager,
-        './SubscriptionLocator': this.SubscriptionLocator,
-        '../User/UserAuditLogHandler': this.UserAuditLogHandler,
-        '../Errors/ErrorController': this.ErrorController,
-        '../User/UserGetter': this.UserGetter,
-        '../Email/EmailHandler': this.EmailHandler,
-        '../../infrastructure/RateLimiter': this.RateLimiter,
-        '../../infrastructure/Modules': (this.Modules = {
-          promises: {
-            hooks: {
-              fire: sinon.stub().resolves([]),
-            },
+    this.Controller = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Subscription/TeamInvitesHandler':
+        this.TeamInvitesHandler,
+      '../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
+      '../../../../app/src/Features/Subscription/SubscriptionLocator':
+        this.SubscriptionLocator,
+      '../../../../app/src/Features/User/UserAuditLogHandler':
+        this.UserAuditLogHandler,
+      '../../../../app/src/Features/Errors/ErrorController':
+        this.ErrorController,
+      '../../../../app/src/Features/User/UserGetter': this.UserGetter,
+      '../../../../app/src/Features/Email/EmailHandler': this.EmailHandler,
+      '../../../../app/src/infrastructure/RateLimiter': this.RateLimiter,
+      '../../../../app/src/infrastructure/Modules': (this.Modules = {
+        promises: {
+          hooks: {
+            fire: sinon.stub().resolves([]),
           },
           },
-        }),
-        '../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
+        },
+      }),
+      '../../../../app/src/Features/SplitTests/SplitTestHandler':
+        (this.SplitTestHandler = {
           promises: {
           promises: {
             getAssignment: sinon.stub().resolves({}),
             getAssignment: sinon.stub().resolves({}),
           },
           },
         }),
         }),
-      },
     })
     })
   })
   })
 
 

+ 12 - 13
services/web/test/unit/src/Tags/TagsControllerTests.js → services/web/test/unit/src/Tags/TagsControllerTests.mjs

@@ -1,16 +1,16 @@
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const { assert } = require('chai')
-const modulePath = require('path').join(
-  __dirname,
-  '../../../../app/src/Features/Tags/TagsController.js'
-)
+import esmock from 'esmock'
+import sinon from 'sinon'
+import { assert } from 'chai'
+const modulePath = new URL(
+  '../../../../app/src/Features/Tags/TagsController.mjs',
+  import.meta.url
+).pathname
 
 
 describe('TagsController', function () {
 describe('TagsController', function () {
   const userId = '123nd3ijdks'
   const userId = '123nd3ijdks'
   const projectId = '123njdskj9jlk'
   const projectId = '123njdskj9jlk'
 
 
-  beforeEach(function () {
+  beforeEach(async function () {
     this.TagsHandler = {
     this.TagsHandler = {
       promises: {
       promises: {
         addProjectToTag: sinon.stub().resolves(),
         addProjectToTag: sinon.stub().resolves(),
@@ -28,11 +28,10 @@ describe('TagsController', function () {
         return session.user._id
         return session.user._id
       },
       },
     }
     }
-    this.TagsController = SandboxedModule.require(modulePath, {
-      requires: {
-        './TagsHandler': this.TagsHandler,
-        '../Authentication/SessionManager': this.SessionManager,
-      },
+    this.TagsController = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/Tags/TagsHandler': this.TagsHandler,
+      '../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
     })
     })
     this.req = {
     this.req = {
       params: {
       params: {

+ 28 - 20
services/web/test/unit/src/ThirdPartyDataStore/TpdsControllerTests.js → services/web/test/unit/src/ThirdPartyDataStore/TpdsControllerTests.mjs

@@ -1,16 +1,18 @@
-const { ObjectId } = require('mongodb-legacy')
-const { expect } = require('chai')
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
-const MockResponse = require('../helpers/MockResponse')
-const MockRequest = require('../helpers/MockRequest')
+import mongodb from 'mongodb-legacy'
+import { expect } from 'chai'
+import esmock from 'esmock'
+import sinon from 'sinon'
+import Errors from '../../../../app/src/Features/Errors/Errors.js'
+import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.js'
+
+const ObjectId = mongodb.ObjectId
 
 
 const MODULE_PATH =
 const MODULE_PATH =
-  '../../../../app/src/Features/ThirdPartyDataStore/TpdsController.js'
+  '../../../../app/src/Features/ThirdPartyDataStore/TpdsController.mjs'
 
 
 describe('TpdsController', function () {
 describe('TpdsController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.metadata = {
     this.metadata = {
       projectId: new ObjectId(),
       projectId: new ObjectId(),
       entityId: new ObjectId(),
       entityId: new ObjectId(),
@@ -55,17 +57,23 @@ describe('TpdsController', function () {
         generateUniqueName: sinon.stub().resolves('unique'),
         generateUniqueName: sinon.stub().resolves('unique'),
       },
       },
     }
     }
-    this.TpdsController = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        './TpdsUpdateHandler': this.TpdsUpdateHandler,
-        './UpdateMerger': this.UpdateMerger,
-        '../Notifications/NotificationsBuilder': this.NotificationsBuilder,
-        '../Authentication/SessionManager': this.SessionManager,
-        '../Errors/HttpErrorHandler': this.HttpErrorHandler,
-        './TpdsQueueManager': this.TpdsQueueManager,
-        '../Project/ProjectCreationHandler': this.ProjectCreationHandler,
-        '../Project/ProjectDetailsHandler': this.ProjectDetailsHandler,
-      },
+    this.TpdsController = await esmock.strict(MODULE_PATH, {
+      '../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateHandler':
+        this.TpdsUpdateHandler,
+      '../../../../app/src/Features/ThirdPartyDataStore/UpdateMerger':
+        this.UpdateMerger,
+      '../../../../app/src/Features/Notifications/NotificationsBuilder':
+        this.NotificationsBuilder,
+      '../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
+      '../../../../app/src/Features/Errors/HttpErrorHandler':
+        this.HttpErrorHandler,
+      '../../../../app/src/Features/ThirdPartyDataStore/TpdsQueueManager':
+        this.TpdsQueueManager,
+      '../../../../app/src/Features/Project/ProjectCreationHandler':
+        this.ProjectCreationHandler,
+      '../../../../app/src/Features/Project/ProjectDetailsHandler':
+        this.ProjectDetailsHandler,
     })
     })
 
 
     this.user_id = 'dsad29jlkjas'
     this.user_id = 'dsad29jlkjas'

+ 27 - 20
services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateHandlerTests.js → services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateHandlerTests.mjs

@@ -1,14 +1,16 @@
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const { expect } = require('chai')
-const { ObjectId } = require('mongodb-legacy')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
+import esmock from 'esmock'
+import sinon from 'sinon'
+import { expect } from 'chai'
+import mongodb from 'mongodb-legacy'
+import Errors from '../../../../app/src/Features/Errors/Errors.js'
+
+const ObjectId = mongodb.ObjectId
 
 
 const MODULE_PATH =
 const MODULE_PATH =
-  '../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateHandler.js'
+  '../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateHandler.mjs'
 
 
 describe('TpdsUpdateHandler', function () {
 describe('TpdsUpdateHandler', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.projectName = 'My recipes'
     this.projectName = 'My recipes'
     this.projects = {
     this.projects = {
       active1: { _id: new ObjectId(), name: this.projectName },
       active1: { _id: new ObjectId(), name: this.projectName },
@@ -95,19 +97,24 @@ describe('TpdsUpdateHandler', function () {
       },
       },
     }
     }
 
 
-    this.TpdsUpdateHandler = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        '../Cooldown/CooldownManager': this.CooldownManager,
-        '../Uploads/FileTypeManager': this.FileTypeManager,
-        '../../infrastructure/Modules': this.Modules,
-        '../Notifications/NotificationsBuilder': this.NotificationsBuilder,
-        '../Project/ProjectCreationHandler': this.ProjectCreationHandler,
-        '../Project/ProjectDeleter': this.ProjectDeleter,
-        '../Project/ProjectGetter': this.ProjectGetter,
-        '../Project/ProjectHelper': this.ProjectHelper,
-        '../Project/ProjectRootDocManager': this.RootDocManager,
-        './UpdateMerger': this.UpdateMerger,
-      },
+    this.TpdsUpdateHandler = await esmock.strict(MODULE_PATH, {
+      '.../../../../app/src/Features/Cooldown/CooldownManager':
+        this.CooldownManager,
+      '../../../../app/src/Features/Uploads/FileTypeManager':
+        this.FileTypeManager,
+      '../../../../app/src/infrastructure/Modules': this.Modules,
+      '../../../../app/src/Features/Notifications/NotificationsBuilder':
+        this.NotificationsBuilder,
+      '../../../../app/src/Features/Project/ProjectCreationHandler':
+        this.ProjectCreationHandler,
+      '../../../../app/src/Features/Project/ProjectDeleter':
+        this.ProjectDeleter,
+      '../../../../app/src/Features/Project/ProjectGetter': this.ProjectGetter,
+      '../../../../app/src/Features/Project/ProjectHelper': this.ProjectHelper,
+      '../../../../app/src/Features/Project/ProjectRootDocManager':
+        this.RootDocManager,
+      '../../../../app/src/Features/ThirdPartyDataStore/UpdateMerger':
+        this.UpdateMerger,
     })
     })
   })
   })
 
 

+ 58 - 32
services/web/test/unit/src/TokenAccess/TokenAccessControllerTests.js → services/web/test/unit/src/TokenAccess/TokenAccessControllerTests.mjs

@@ -1,16 +1,19 @@
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const { expect } = require('chai')
-const { ObjectId } = require('mongodb-legacy')
-const MockRequest = require('../helpers/MockRequest')
-const MockResponse = require('../helpers/MockResponse')
-const PrivilegeLevels = require('../../../../app/src/Features/Authorization/PrivilegeLevels')
+import esmock from 'esmock'
+import sinon from 'sinon'
+import { expect } from 'chai'
+import mongodb from 'mongodb-legacy'
+import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.js'
+import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.js'
+import { getSafeRedirectPath } from '../../../../app/src/Features/Helpers/UrlHelper.js'
+
+const ObjectId = mongodb.ObjectId
 
 
 const MODULE_PATH =
 const MODULE_PATH =
   '../../../../app/src/Features/TokenAccess/TokenAccessController'
   '../../../../app/src/Features/TokenAccess/TokenAccessController'
 
 
 describe('TokenAccessController', function () {
 describe('TokenAccessController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.token = 'abc123'
     this.token = 'abc123'
     this.user = { _id: new ObjectId() }
     this.user = { _id: new ObjectId() }
     this.project = {
     this.project = {
@@ -137,32 +140,54 @@ describe('TokenAccessController', function () {
       },
       },
     }
     }
 
 
-    this.TokenAccessController = SandboxedModule.require(MODULE_PATH, {
-      requires: {
-        '@overleaf/settings': this.Settings,
-        './TokenAccessHandler': this.TokenAccessHandler,
-        '../Authentication/AuthenticationController':
-          this.AuthenticationController,
-        '../Authentication/SessionManager': this.SessionManager,
-        '../Authorization/AuthorizationManager': this.AuthorizationManager,
-        '../Authorization/AuthorizationMiddleware':
-          this.AuthorizationMiddleware,
-        '../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
-        '../SplitTests/SplitTestHandler': this.SplitTestHandler,
-        '../Errors/Errors': (this.Errors = { NotFoundError: sinon.stub() }),
-        '../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
-        '../Collaborators/CollaboratorsInviteHandler':
-          this.CollaboratorsInviteHandler,
-        '../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
-        '../Editor/EditorRealTimeController': this.EditorRealTimeController,
-        '../Project/ProjectGetter': this.ProjectGetter,
-        '../Helpers/AsyncFormHelper': (this.AsyncFormHelper = {
+    this.TokenAccessController = await esmock.strict(MODULE_PATH, {
+      '@overleaf/settings': this.Settings,
+      '../../../../app/src/Features/TokenAccess/TokenAccessHandler':
+        this.TokenAccessHandler,
+      '../../../../app/src/Features/Authentication/AuthenticationController':
+        this.AuthenticationController,
+      '../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
+      '../../../../app/src/Features/Authorization/AuthorizationManager':
+        this.AuthorizationManager,
+      '../../../../app/src/Features/Authorization/AuthorizationMiddleware':
+        this.AuthorizationMiddleware,
+      '../../../../app/src/Features/Project/ProjectAuditLogHandler':
+        this.ProjectAuditLogHandler,
+      '../../../../app/src/Features/SplitTests/SplitTestHandler':
+        this.SplitTestHandler,
+      '../../../../app/src/Features/Errors/Errors': (this.Errors = {
+        NotFoundError: sinon.stub(),
+      }),
+      '../../../../app/src/Features/Collaborators/CollaboratorsHandler':
+        this.CollaboratorsHandler,
+      '../../../../app/src/Features/Collaborators/CollaboratorsInviteHandler':
+        this.CollaboratorsInviteHandler,
+      '../../../../app/src/Features/Collaborators/CollaboratorsGetter':
+        this.CollaboratorsGetter,
+      '../../../../app/src/Features/Editor/EditorRealTimeController':
+        this.EditorRealTimeController,
+      '../../../../app/src/Features/Project/ProjectGetter': this.ProjectGetter,
+      '../../../../app/src/Features/Helpers/AsyncFormHelper':
+        (this.AsyncFormHelper = {
           redirect: sinon.stub(),
           redirect: sinon.stub(),
         }),
         }),
-        '../Analytics/AnalyticsManager': this.AnalyticsManager,
-        '../User/UserGetter': this.UserGetter,
-        '../Subscription/LimitationsManager': this.LimitationsManager,
-      },
+      '../../../../app/src/Features/Helpers/AdminAuthorizationHelper':
+        (this.AdminAuthorizationHelper = {
+          canRedirectToAdminDomain: sinon.stub(),
+        }),
+      '../../../../app/src/Features/Helpers/UrlHelper': (this.UrlHelper = {
+        getSafeAdminDomainRedirect: sinon
+          .stub()
+          .callsFake(
+            path => `${this.Settings.adminUrl}${getSafeRedirectPath(path)}`
+          ),
+      }),
+      '../../../../app/src/Features/Analytics/AnalyticsManager':
+        this.AnalyticsManager,
+      '../../../../app/src/Features/User/UserGetter': this.UserGetter,
+      '../../../../app/src/Features/Subscription/LimitationsManager':
+        this.LimitationsManager,
     })
     })
   })
   })
 
 
@@ -757,6 +782,7 @@ describe('TokenAccessController', function () {
       beforeEach(function () {
       beforeEach(function () {
         this.SessionManager.getLoggedInUserId.returns(admin._id)
         this.SessionManager.getLoggedInUserId.returns(admin._id)
         this.SessionManager.getSessionUser.returns(admin)
         this.SessionManager.getSessionUser.returns(admin)
+        this.AdminAuthorizationHelper.canRedirectToAdminDomain.returns(true)
         this.req.params = { token: this.token }
         this.req.params = { token: this.token }
         this.req.body = { confirmedByUser: true, tokenHashPrefix: '#prefix' }
         this.req.body = { confirmedByUser: true, tokenHashPrefix: '#prefix' }
       })
       })

+ 27 - 26
services/web/test/unit/src/Uploads/ProjectUploadControllerTests.js → services/web/test/unit/src/Uploads/ProjectUploadControllerTests.mjs

@@ -1,8 +1,3 @@
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
 // TODO: This file was created by bulk-decaffeinate.
 // TODO: This file was created by bulk-decaffeinate.
 // Fix any style issues and re-enable lint.
 // Fix any style issues and re-enable lint.
 /*
 /*
@@ -11,17 +6,20 @@
  * DS206: Consider reworking classes to avoid initClass
  * DS206: Consider reworking classes to avoid initClass
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
-const sinon = require('sinon')
-const { expect } = require('chai')
+import sinon from 'sinon'
+
+import { expect } from 'chai'
+
+import esmock from 'esmock'
+import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.js'
+import ArchiveErrors from '../../../../app/src/Features/Uploads/ArchiveErrors.js'
+
 const modulePath =
 const modulePath =
-  '../../../../app/src/Features/Uploads/ProjectUploadController.js'
-const SandboxedModule = require('sandboxed-module')
-const MockRequest = require('../helpers/MockRequest')
-const MockResponse = require('../helpers/MockResponse')
-const ArchiveErrors = require('../../../../app/src/Features/Uploads/ArchiveErrors')
+  '../../../../app/src/Features/Uploads/ProjectUploadController.mjs'
 
 
 describe('ProjectUploadController', function () {
 describe('ProjectUploadController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     let Timer
     let Timer
     this.req = new MockRequest()
     this.req = new MockRequest()
     this.res = new MockResponse()
     this.res = new MockResponse()
@@ -47,19 +45,22 @@ describe('ProjectUploadController', function () {
       promises: {},
       promises: {},
     }
     }
 
 
-    return (this.ProjectUploadController = SandboxedModule.require(modulePath, {
-      requires: {
-        multer: sinon.stub(),
-        '@overleaf/settings': { path: {} },
-        './ProjectUploadManager': (this.ProjectUploadManager = {}),
-        './FileSystemImportManager': (this.FileSystemImportManager = {}),
-        '@overleaf/metrics': this.metrics,
-        '../Authentication/SessionManager': this.SessionManager,
-        './ArchiveErrors': ArchiveErrors,
-        '../Project/ProjectLocator': this.ProjectLocator,
-        '../Editor/EditorController': this.EditorController,
-        fs: (this.fs = {}),
-      },
+    return (this.ProjectUploadController = await esmock.strict(modulePath, {
+      multer: sinon.stub(),
+      '@overleaf/settings': { path: {} },
+      '../../../../app/src/Features/Uploads/ProjectUploadManager':
+        (this.ProjectUploadManager = {}),
+      '../../../../app/src/Features/Uploads/FileSystemImportManager':
+        (this.FileSystemImportManager = {}),
+      '@overleaf/metrics': this.metrics,
+      '../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
+      '../../../../app/src/Features/Uploads/ArchiveErrors': ArchiveErrors,
+      '../../../../app/src/Features/Project/ProjectLocator':
+        this.ProjectLocator,
+      '../../../../app/src/Features/Editor/EditorController':
+        this.EditorController,
+      fs: (this.fs = {}),
     }))
     }))
   })
   })
 
 

+ 34 - 30
services/web/test/unit/src/User/UserPagesControllerTests.js → services/web/test/unit/src/User/UserPagesControllerTests.mjs

@@ -1,17 +1,17 @@
-const SandboxedModule = require('sandboxed-module')
-const assert = require('assert')
-const path = require('path')
-const sinon = require('sinon')
-const modulePath = path.join(
-  __dirname,
-  '../../../../app/src/Features/User/UserPagesController'
-)
-const { expect } = require('chai')
-const MockResponse = require('../helpers/MockResponse')
-const MockRequest = require('../helpers/MockRequest')
+import esmock from 'esmock'
+import assert from 'assert'
+import sinon from 'sinon'
+import { expect } from 'chai'
+import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequest.js'
+
+const modulePath = new URL(
+  '../../../../app/src/Features/User/UserPagesController',
+  import.meta.url
+).pathname
 
 
 describe('UserPagesController', function () {
 describe('UserPagesController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.settings = {
     this.settings = {
       apis: {
       apis: {
         v1: {
         v1: {
@@ -85,24 +85,28 @@ describe('UserPagesController', function () {
         },
         },
       },
       },
     }
     }
-    this.UserPagesController = SandboxedModule.require(modulePath, {
-      requires: {
-        '@overleaf/settings': this.settings,
-        './UserGetter': this.UserGetter,
-        './UserSessionsManager': this.UserSessionsManager,
-        '../Newsletter/NewsletterManager': this.NewsletterManager,
-        '../Errors/ErrorController': this.ErrorController,
-        '../Authentication/AuthenticationController':
-          this.AuthenticationController,
-        '../Subscription/SubscriptionLocator': this.SubscriptionLocator,
-        '../../infrastructure/Features': this.Features,
-        '../../../../modules/oauth2-server/app/src/OAuthPersonalAccessTokenManager':
-          this.PersonalAccessTokenManager,
-        '../Authentication/SessionManager': this.SessionManager,
-        '../SplitTests/SplitTestHandler': this.SplitTestHandler,
-        '../../infrastructure/Modules': this.Modules,
-        request: (this.request = sinon.stub()),
-      },
+    this.UserPagesController = await esmock.strict(modulePath, {
+      '@overleaf/settings': this.settings,
+      '../../../../app/src/Features/User/UserGetter': this.UserGetter,
+      '../../../../app/src/Features/User/UserSessionsManager':
+        this.UserSessionsManager,
+      '../../../../app/src/Features/Newsletter/NewsletterManager':
+        this.NewsletterManager,
+      '../../../../app/src/Features/Errors/ErrorController':
+        this.ErrorController,
+      '../../../../app/src/Features/Authentication/AuthenticationController':
+        this.AuthenticationController,
+      '../../../../app/src/Features/Subscription/SubscriptionLocator':
+        this.SubscriptionLocator,
+      '../../../../app/src/infrastructure/Features': this.Features,
+      '../../../../modules/oauth2-server/app/src/OAuthPersonalAccessTokenManager':
+        this.PersonalAccessTokenManager,
+      '../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
+      '../../../../app/src/Features/SplitTests/SplitTestHandler':
+        this.SplitTestHandler,
+      '../../../../app/src/infrastructure/Modules': this.Modules,
+      request: (this.request = sinon.stub()),
     })
     })
     this.req = new MockRequest()
     this.req = new MockRequest()
     this.req.session.user = this.user
     this.req.session.user = this.user

+ 27 - 25
services/web/test/unit/src/UserMembership/UserMembershipControllerTests.js → services/web/test/unit/src/UserMembership/UserMembershipControllerTests.mjs

@@ -1,21 +1,22 @@
-const sinon = require('sinon')
-const assertCalledWith = sinon.assert.calledWith
-const { expect } = require('chai')
-const modulePath =
-  '../../../../app/src/Features/UserMembership/UserMembershipController.js'
-const SandboxedModule = require('sandboxed-module')
-const MockRequest = require('../helpers/MockRequest')
-const MockResponse = require('../helpers/MockResponse')
-const EntityConfigs = require('../../../../app/src/Features/UserMembership/UserMembershipEntityConfigs')
-const Errors = require('../../../../app/src/Features/Errors/Errors')
-const {
+import sinon from 'sinon'
+import { expect } from 'chai'
+import esmock from 'esmock'
+import MockRequest from '../helpers/MockRequest.js'
+import MockResponse from '../helpers/MockResponse.js'
+import EntityConfigs from '../../../../app/src/Features/UserMembership/UserMembershipEntityConfigs.js'
+import Errors from '../../../../app/src/Features/Errors/Errors.js'
+import {
   UserIsManagerError,
   UserIsManagerError,
   UserNotFoundError,
   UserNotFoundError,
   UserAlreadyAddedError,
   UserAlreadyAddedError,
-} = require('../../../../app/src/Features/UserMembership/UserMembershipErrors')
+} from '../../../../app/src/Features/UserMembership/UserMembershipErrors.js'
+const assertCalledWith = sinon.assert.calledWith
+
+const modulePath =
+  '../../../../app/src/Features/UserMembership/UserMembershipController.mjs'
 
 
 describe('UserMembershipController', function () {
 describe('UserMembershipController', function () {
-  beforeEach(function () {
+  beforeEach(async function () {
     this.req = new MockRequest()
     this.req = new MockRequest()
     this.req.params.id = 'mock-entity-id'
     this.req.params.id = 'mock-entity-id'
     this.user = { _id: 'mock-user-id' }
     this.user = { _id: 'mock-user-id' }
@@ -79,19 +80,20 @@ describe('UserMembershipController', function () {
       },
       },
       getAssignment: sinon.stub().yields(null, { variant: 'default' }),
       getAssignment: sinon.stub().yields(null, { variant: 'default' }),
     }
     }
-    this.UserMembershipController = SandboxedModule.require(modulePath, {
-      requires: {
-        './UserMembershipErrors': {
-          UserIsManagerError,
-          UserNotFoundError,
-          UserAlreadyAddedError,
-        },
-        '../Authentication/SessionManager': this.SessionManager,
-        '../SplitTests/SplitTestHandler': this.SplitTestHandler,
-        './UserMembershipHandler': this.UserMembershipHandler,
-        '@overleaf/settings': this.Settings,
-        '../../models/SSOConfig': { SSOConfig: this.SSOConfig },
+    this.UserMembershipController = await esmock.strict(modulePath, {
+      '../../../../app/src/Features/UserMembership/UserMembershipErrors': {
+        UserIsManagerError,
+        UserNotFoundError,
+        UserAlreadyAddedError,
       },
       },
+      '../../../../app/src/Features/Authentication/SessionManager':
+        this.SessionManager,
+      '../../../../app/src/Features/SplitTests/SplitTestHandler':
+        this.SplitTestHandler,
+      '../../../../app/src/Features/UserMembership/UserMembershipHandler':
+        this.UserMembershipHandler,
+      '@overleaf/settings': this.Settings,
+      '../../../../app/src/models/SSOConfig': { SSOConfig: this.SSOConfig },
     })
     })
   })
   })