diff --git a/services/web/.eslintrc.js b/services/web/.eslintrc.js
index 8056ffd48b..f8602a5b92 100644
--- a/services/web/.eslintrc.js
+++ b/services/web/.eslintrc.js
@@ -137,6 +137,8 @@ module.exports = {
'app.mjs',
'scripts/**/*.mjs',
'migrations/**/*.mjs',
+ 'test/acceptance/src/**/*.mjs',
+ 'test/unit/src/**/*.mjs',
],
excludedFiles: [
// migration template file
diff --git a/services/web/app/src/Features/Analytics/AnalyticsController.mjs b/services/web/app/src/Features/Analytics/AnalyticsController.mjs
index 2c9305dd50..76b5452e58 100644
--- a/services/web/app/src/Features/Analytics/AnalyticsController.mjs
+++ b/services/web/app/src/Features/Analytics/AnalyticsController.mjs
@@ -2,7 +2,7 @@ import metrics from '@overleaf/metrics'
import AnalyticsManager from './AnalyticsManager.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
import GeoIpLookup from '../../infrastructure/GeoIpLookup.mjs'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import { expressify } from '@overleaf/promise-utils'
import AccountMappingHelper from './AccountMappingHelper.mjs'
diff --git a/services/web/app/src/Features/Analytics/AnalyticsManager.mjs b/services/web/app/src/Features/Analytics/AnalyticsManager.mjs
index 272cf68497..014934905c 100644
--- a/services/web/app/src/Features/Analytics/AnalyticsManager.mjs
+++ b/services/web/app/src/Features/Analytics/AnalyticsManager.mjs
@@ -2,7 +2,7 @@ import SessionManager from '../Authentication/SessionManager.mjs'
import UserAnalyticsIdCache from './UserAnalyticsIdCache.mjs'
import Settings from '@overleaf/settings'
import Metrics from '../../infrastructure/Metrics.js'
-import Queues from '../../infrastructure/Queues.js'
+import Queues from '../../infrastructure/Queues.mjs'
import crypto, { createHash } from 'node:crypto'
import _ from 'lodash'
import { expressify } from '@overleaf/promise-utils'
diff --git a/services/web/app/src/Features/Authentication/AuthenticationController.mjs b/services/web/app/src/Features/Authentication/AuthenticationController.mjs
index f698c2fd3e..7f6187dfc2 100644
--- a/services/web/app/src/Features/Authentication/AuthenticationController.mjs
+++ b/services/web/app/src/Features/Authentication/AuthenticationController.mjs
@@ -1,7 +1,7 @@
import AuthenticationManager from './AuthenticationManager.mjs'
import SessionManager from './SessionManager.mjs'
import OError from '@overleaf/o-error'
-import LoginRateLimiter from '../Security/LoginRateLimiter.js'
+import LoginRateLimiter from '../Security/LoginRateLimiter.mjs'
import UserUpdater from '../User/UserUpdater.mjs'
import Metrics from '@overleaf/metrics'
import logger from '@overleaf/logger'
@@ -23,7 +23,7 @@ import { acceptsJson } from '../../infrastructure/RequestContentTypeDetection.js
import AdminAuthorizationHelper from '../Helpers/AdminAuthorizationHelper.mjs'
import Modules from '../../infrastructure/Modules.js'
import { expressify, promisify } from '@overleaf/promise-utils'
-import { handleAuthenticateErrors } from './AuthenticationErrors.js'
+import { handleAuthenticateErrors } from './AuthenticationErrors.mjs'
import EmailHelper from '../Helpers/EmailHelper.mjs'
const { hasAdminAccess } = AdminAuthorizationHelper
diff --git a/services/web/app/src/Features/Authentication/AuthenticationErrors.js b/services/web/app/src/Features/Authentication/AuthenticationErrors.mjs
similarity index 67%
rename from services/web/app/src/Features/Authentication/AuthenticationErrors.js
rename to services/web/app/src/Features/Authentication/AuthenticationErrors.mjs
index c5dc8bac33..dc1d0cbd8d 100644
--- a/services/web/app/src/Features/Authentication/AuthenticationErrors.js
+++ b/services/web/app/src/Features/Authentication/AuthenticationErrors.mjs
@@ -1,15 +1,15 @@
-const Metrics = require('@overleaf/metrics')
-const OError = require('@overleaf/o-error')
-const Settings = require('@overleaf/settings')
-const Errors = require('../Errors/Errors')
+import Metrics from '@overleaf/metrics'
+import OError from '@overleaf/o-error'
+import Settings from '@overleaf/settings'
+import Errors from '../Errors/Errors.js'
-class InvalidEmailError extends Errors.BackwardCompatibleError {}
-class InvalidPasswordError extends Errors.BackwardCompatibleError {}
-class ParallelLoginError extends Errors.BackwardCompatibleError {}
-class PasswordMustBeDifferentError extends Errors.BackwardCompatibleError {}
-class PasswordReusedError extends Errors.BackwardCompatibleError {}
+export class InvalidEmailError extends Errors.BackwardCompatibleError {}
+export class InvalidPasswordError extends Errors.BackwardCompatibleError {}
+export class ParallelLoginError extends Errors.BackwardCompatibleError {}
+export class PasswordMustBeDifferentError extends Errors.BackwardCompatibleError {}
+export class PasswordReusedError extends Errors.BackwardCompatibleError {}
-function handleAuthenticateErrors(error, req) {
+export function handleAuthenticateErrors(error, req) {
if (error.message === 'password is too long') {
Metrics.inc('login_failure_reason', 1, {
status: 'password_is_too_long',
@@ -48,7 +48,7 @@ function handleAuthenticateErrors(error, req) {
throw error
}
-module.exports = {
+export default {
InvalidEmailError,
InvalidPasswordError,
ParallelLoginError,
diff --git a/services/web/app/src/Features/Authentication/AuthenticationManager.mjs b/services/web/app/src/Features/Authentication/AuthenticationManager.mjs
index 0d45c4eaa8..ab24e0e41e 100644
--- a/services/web/app/src/Features/Authentication/AuthenticationManager.mjs
+++ b/services/web/app/src/Features/Authentication/AuthenticationManager.mjs
@@ -10,7 +10,7 @@ import {
ParallelLoginError,
PasswordMustBeDifferentError,
PasswordReusedError,
-} from './AuthenticationErrors.js'
+} from './AuthenticationErrors.mjs'
import { callbackify, callbackifyMultiResult } from '@overleaf/promise-utils'
import HaveIBeenPwned from './HaveIBeenPwned.mjs'
diff --git a/services/web/app/src/Features/Authorization/AuthorizationManager.mjs b/services/web/app/src/Features/Authorization/AuthorizationManager.mjs
index 7a9be587b9..dd9c19ce30 100644
--- a/services/web/app/src/Features/Authorization/AuthorizationManager.mjs
+++ b/services/web/app/src/Features/Authorization/AuthorizationManager.mjs
@@ -1,11 +1,11 @@
import { callbackify } from 'node:util'
import mongodb from 'mongodb-legacy'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import CollaboratorsGetter from '../Collaborators/CollaboratorsGetter.mjs'
import CollaboratorsHandler from '../Collaborators/CollaboratorsHandler.mjs'
import ProjectGetter from '../Project/ProjectGetter.mjs'
import { User } from '../../models/User.mjs'
-import PrivilegeLevels from './PrivilegeLevels.js'
+import PrivilegeLevels from './PrivilegeLevels.mjs'
import TokenAccessHandler from '../TokenAccess/TokenAccessHandler.mjs'
import PublicAccessLevels from './PublicAccessLevels.mjs'
import Errors from '../Errors/Errors.js'
diff --git a/services/web/app/src/Features/Authorization/PermissionsController.mjs b/services/web/app/src/Features/Authorization/PermissionsController.mjs
index 25b9702547..9dd6bf6300 100644
--- a/services/web/app/src/Features/Authorization/PermissionsController.mjs
+++ b/services/web/app/src/Features/Authorization/PermissionsController.mjs
@@ -3,7 +3,7 @@ import { ForbiddenError, UserNotFoundError } from '../Errors/Errors.js'
import PermissionsManager from './PermissionsManager.mjs'
import Modules from '../../infrastructure/Modules.js'
import { expressify } from '@overleaf/promise-utils'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
/**
* @typedef {(import('express').Request)} Request
diff --git a/services/web/app/src/Features/Authorization/PrivilegeLevels.js b/services/web/app/src/Features/Authorization/PrivilegeLevels.mjs
similarity index 86%
rename from services/web/app/src/Features/Authorization/PrivilegeLevels.js
rename to services/web/app/src/Features/Authorization/PrivilegeLevels.mjs
index e78669954c..fcf42488bc 100644
--- a/services/web/app/src/Features/Authorization/PrivilegeLevels.js
+++ b/services/web/app/src/Features/Authorization/PrivilegeLevels.mjs
@@ -9,4 +9,4 @@ const PrivilegeLevels = {
OWNER: 'owner',
}
-module.exports = PrivilegeLevels
+export default PrivilegeLevels
diff --git a/services/web/app/src/Features/BrandVariations/BrandVariationsHandler.mjs b/services/web/app/src/Features/BrandVariations/BrandVariationsHandler.mjs
index f66c573f2e..ba6c19777d 100644
--- a/services/web/app/src/Features/BrandVariations/BrandVariationsHandler.mjs
+++ b/services/web/app/src/Features/BrandVariations/BrandVariationsHandler.mjs
@@ -2,7 +2,7 @@ import OError from '@overleaf/o-error'
import { URL } from 'node:url'
import settings from '@overleaf/settings'
import logger from '@overleaf/logger'
-import V1Api from '../V1/V1Api.js'
+import V1Api from '../V1/V1Api.mjs'
import sanitizeHtml from 'sanitize-html'
import { promisify } from '@overleaf/promise-utils'
diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsController.mjs b/services/web/app/src/Features/Collaborators/CollaboratorsController.mjs
index d1769c9203..b70553218e 100644
--- a/services/web/app/src/Features/Collaborators/CollaboratorsController.mjs
+++ b/services/web/app/src/Features/Collaborators/CollaboratorsController.mjs
@@ -14,9 +14,9 @@ import AdminAuthorizationHelper from '../Helpers/AdminAuthorizationHelper.mjs'
import TokenAccessHandler from '../TokenAccess/TokenAccessHandler.mjs'
import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.mjs'
import LimitationsManager from '../Subscription/LimitationsManager.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import { z, zz, validateReq } from '../../infrastructure/Validation.js'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
const { hasAdminAccess } = AdminAuthorizationHelper
const ObjectId = mongodb.ObjectId
diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsGetter.mjs b/services/web/app/src/Features/Collaborators/CollaboratorsGetter.mjs
index b75a06f955..ec77e51c22 100644
--- a/services/web/app/src/Features/Collaborators/CollaboratorsGetter.mjs
+++ b/services/web/app/src/Features/Collaborators/CollaboratorsGetter.mjs
@@ -11,7 +11,7 @@ import PublicAccessLevels from '../Authorization/PublicAccessLevels.mjs'
import Errors from '../Errors/Errors.js'
import ProjectEditorHandler from '../Project/ProjectEditorHandler.mjs'
import Sources from '../Authorization/Sources.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
const { ObjectId } = mongodb
diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsHandler.mjs b/services/web/app/src/Features/Collaborators/CollaboratorsHandler.mjs
index ab7f366315..a25837f57e 100644
--- a/services/web/app/src/Features/Collaborators/CollaboratorsHandler.mjs
+++ b/services/web/app/src/Features/Collaborators/CollaboratorsHandler.mjs
@@ -4,7 +4,7 @@ import { Project } from '../../models/Project.mjs'
import ProjectGetter from '../Project/ProjectGetter.mjs'
import logger from '@overleaf/logger'
import ContactManager from '../Contacts/ContactManager.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import TpdsProjectFlusher from '../ThirdPartyDataStore/TpdsProjectFlusher.mjs'
import CollaboratorsGetter from './CollaboratorsGetter.mjs'
import Errors from '../Errors/Errors.js'
diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs b/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs
index 75d280701e..aca557934a 100644
--- a/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs
+++ b/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.mjs
@@ -16,7 +16,7 @@ import { expressify } from '@overleaf/promise-utils'
import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.mjs'
import Errors from '../Errors/Errors.js'
import AuthenticationController from '../Authentication/AuthenticationController.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
// This rate limiter allows a different number of requests depending on the
// number of callaborators a user is allowed. This is implemented by providing
diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsInviteGetter.mjs b/services/web/app/src/Features/Collaborators/CollaboratorsInviteGetter.mjs
index 8ce4651e49..e7903c900f 100644
--- a/services/web/app/src/Features/Collaborators/CollaboratorsInviteGetter.mjs
+++ b/services/web/app/src/Features/Collaborators/CollaboratorsInviteGetter.mjs
@@ -1,6 +1,6 @@
import logger from '@overleaf/logger'
import { ProjectInvite } from '../../models/ProjectInvite.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import CollaboratorsInviteHelper from './CollaboratorsInviteHelper.mjs'
async function getAllInvites(projectId) {
diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.mjs b/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.mjs
index 4c956dc102..d6f745b556 100644
--- a/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.mjs
+++ b/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.mjs
@@ -8,7 +8,7 @@ import CollaboratorsInviteHelper from './CollaboratorsInviteHelper.mjs'
import UserGetter from '../User/UserGetter.mjs'
import ProjectGetter from '../Project/ProjectGetter.mjs'
import NotificationsBuilder from '../Notifications/NotificationsBuilder.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import LimitationsManager from '../Subscription/LimitationsManager.mjs'
import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.mjs'
import _ from 'lodash'
diff --git a/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.mjs b/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.mjs
index 093f6893c8..a0b9bfa054 100644
--- a/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.mjs
+++ b/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.mjs
@@ -5,7 +5,7 @@ import UserGetter from '../User/UserGetter.mjs'
import CollaboratorsHandler from './CollaboratorsHandler.mjs'
import EmailHandler from '../Email/EmailHandler.mjs'
import Errors from '../Errors/Errors.js'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import TpdsProjectFlusher from '../ThirdPartyDataStore/TpdsProjectFlusher.mjs'
import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
diff --git a/services/web/app/src/Features/Compile/ClsiCacheHandler.mjs b/services/web/app/src/Features/Compile/ClsiCacheHandler.mjs
index fd30724768..6c5f20c0d2 100644
--- a/services/web/app/src/Features/Compile/ClsiCacheHandler.mjs
+++ b/services/web/app/src/Features/Compile/ClsiCacheHandler.mjs
@@ -8,7 +8,7 @@ import logger from '@overleaf/logger'
import Settings from '@overleaf/settings'
import OError from '@overleaf/o-error'
import { NotFoundError, InvalidNameError } from '../Errors/Errors.js'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
const TIMEOUT = 4_000
diff --git a/services/web/app/src/Features/Compile/ClsiCacheManager.mjs b/services/web/app/src/Features/Compile/ClsiCacheManager.mjs
index 0cec56cdfb..d61645fef7 100644
--- a/services/web/app/src/Features/Compile/ClsiCacheManager.mjs
+++ b/services/web/app/src/Features/Compile/ClsiCacheManager.mjs
@@ -7,7 +7,7 @@ import UserGetter from '../User/UserGetter.mjs'
import Settings from '@overleaf/settings'
import { fetchJson, RequestFailedError } from '@overleaf/fetch-utils'
import Metrics from '@overleaf/metrics'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
/**
* Get the most recent build and metadata
diff --git a/services/web/app/src/Features/Compile/CompileController.mjs b/services/web/app/src/Features/Compile/CompileController.mjs
index 8e6382c2ec..f1a3a6e8fc 100644
--- a/services/web/app/src/Features/Compile/CompileController.mjs
+++ b/services/web/app/src/Features/Compile/CompileController.mjs
@@ -21,7 +21,7 @@ import {
fetchStreamWithResponse,
RequestFailedError,
} from '@overleaf/fetch-utils'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
const { z, zz, validateReq } = Validation
const ClsiCookieManager = ClsiCookieManagerFactory(
diff --git a/services/web/app/src/Features/Editor/EditorHttpController.mjs b/services/web/app/src/Features/Editor/EditorHttpController.mjs
index 2c79bf3131..9fe2550e4e 100644
--- a/services/web/app/src/Features/Editor/EditorHttpController.mjs
+++ b/services/web/app/src/Features/Editor/EditorHttpController.mjs
@@ -5,7 +5,7 @@ import AuthorizationManager from '../Authorization/AuthorizationManager.mjs'
import ProjectEditorHandler from '../Project/ProjectEditorHandler.mjs'
import Metrics from '@overleaf/metrics'
import CollaboratorsInviteGetter from '../Collaborators/CollaboratorsInviteGetter.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
import Errors from '../Errors/Errors.js'
import { expressify } from '@overleaf/promise-utils'
diff --git a/services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.js b/services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.mjs
similarity index 97%
rename from services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.js
rename to services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.mjs
index fb68ecc7f9..a078cad8e6 100644
--- a/services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.js
+++ b/services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.mjs
@@ -1,6 +1,6 @@
-const _ = require('lodash')
+import _ from 'lodash'
-module.exports = _.template(`\
+export default _.template(`\
diff --git a/services/web/app/src/Features/Email/Bodies/cta-email.js b/services/web/app/src/Features/Email/Bodies/cta-email.mjs
similarity index 98%
rename from services/web/app/src/Features/Email/Bodies/cta-email.js
rename to services/web/app/src/Features/Email/Bodies/cta-email.mjs
index 346793cdb1..6d49620a7d 100644
--- a/services/web/app/src/Features/Email/Bodies/cta-email.js
+++ b/services/web/app/src/Features/Email/Bodies/cta-email.mjs
@@ -1,6 +1,6 @@
-const _ = require('lodash')
+import _ from 'lodash'
-module.exports = _.template(`\
+export default _.template(`\
diff --git a/services/web/app/src/Features/Email/EmailBuilder.mjs b/services/web/app/src/Features/Email/EmailBuilder.mjs
index 294a180a0d..c78e878528 100644
--- a/services/web/app/src/Features/Email/EmailBuilder.mjs
+++ b/services/web/app/src/Features/Email/EmailBuilder.mjs
@@ -1,12 +1,12 @@
import _ from 'lodash'
import settings from '@overleaf/settings'
import moment from 'moment'
-import EmailMessageHelper from './EmailMessageHelper.js'
+import EmailMessageHelper from './EmailMessageHelper.mjs'
import StringHelper from '../Helpers/StringHelper.mjs'
-import BaseWithHeaderEmailLayout from './Layouts/BaseWithHeaderEmailLayout.js'
+import BaseWithHeaderEmailLayout from './Layouts/BaseWithHeaderEmailLayout.mjs'
import SpamSafe from './SpamSafe.mjs'
-import ctaEmailBody from './Bodies/cta-email.js'
-import NoCTAEmailBody from './Bodies/NoCTAEmailBody.js'
+import ctaEmailBody from './Bodies/cta-email.mjs'
+import NoCTAEmailBody from './Bodies/NoCTAEmailBody.mjs'
function _emailBodyPlainText(content, opts, ctaEmail) {
let emailBody = `${content.greeting(opts, true)}`
diff --git a/services/web/app/src/Features/Email/EmailHandler.mjs b/services/web/app/src/Features/Email/EmailHandler.mjs
index ae52acdb82..85a94d5439 100644
--- a/services/web/app/src/Features/Email/EmailHandler.mjs
+++ b/services/web/app/src/Features/Email/EmailHandler.mjs
@@ -3,7 +3,7 @@ import Settings from '@overleaf/settings'
import logger from '@overleaf/logger'
import EmailBuilder from './EmailBuilder.mjs'
import EmailSender from './EmailSender.mjs'
-import Queues from '../../infrastructure/Queues.js'
+import Queues from '../../infrastructure/Queues.mjs'
const EMAIL_SETTINGS = Settings.email || {}
diff --git a/services/web/app/src/Features/Email/EmailMessageHelper.js b/services/web/app/src/Features/Email/EmailMessageHelper.mjs
similarity index 89%
rename from services/web/app/src/Features/Email/EmailMessageHelper.js
rename to services/web/app/src/Features/Email/EmailMessageHelper.mjs
index d8fcc7d120..fe240478ab 100644
--- a/services/web/app/src/Features/Email/EmailMessageHelper.js
+++ b/services/web/app/src/Features/Email/EmailMessageHelper.mjs
@@ -1,4 +1,4 @@
-const sanitizeHtml = require('sanitize-html')
+import sanitizeHtml from 'sanitize-html'
const sanitizeOptions = {
html: {
allowedTags: ['a', 'span', 'b', 'br', 'i'],
@@ -22,7 +22,7 @@ function displayLink(text, url, isPlainText) {
return isPlainText ? `${text} (${url})` : `${text}`
}
-module.exports = {
+export default {
cleanHTML,
displayLink,
}
diff --git a/services/web/app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.js b/services/web/app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.mjs
similarity index 99%
rename from services/web/app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.js
rename to services/web/app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.mjs
index 11546e74c0..33c298d09b 100644
--- a/services/web/app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.js
+++ b/services/web/app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.mjs
@@ -1,7 +1,7 @@
-const _ = require('lodash')
-const settings = require('@overleaf/settings')
+import _ from 'lodash'
+import settings from '@overleaf/settings'
-module.exports = _.template(`\
+export default _.template(`\
diff --git a/services/web/app/src/Features/Helpers/Mongo.js b/services/web/app/src/Features/Helpers/Mongo.mjs
similarity index 86%
rename from services/web/app/src/Features/Helpers/Mongo.js
rename to services/web/app/src/Features/Helpers/Mongo.mjs
index 3ec083a1f5..aecf3b5260 100644
--- a/services/web/app/src/Features/Helpers/Mongo.js
+++ b/services/web/app/src/Features/Helpers/Mongo.mjs
@@ -1,6 +1,8 @@
-const OError = require('@overleaf/o-error')
-const { ObjectId } = require('mongodb-legacy')
-const { ObjectId: MongooseObjectId } = require('mongoose').mongo
+import OError from '@overleaf/o-error'
+import mongodb from 'mongodb-legacy'
+import { ObjectId as MongooseObjectId } from 'mongoose'
+
+const { ObjectId } = mongodb
function _getObjectIdInstance(id) {
if (typeof id === 'string') {
@@ -47,7 +49,7 @@ function isObjectIdInstance(id) {
return id instanceof ObjectId || id instanceof MongooseObjectId
}
-module.exports = {
+export default {
isObjectIdInstance,
normalizeQuery,
normalizeMultiQuery,
diff --git a/services/web/app/src/Features/History/HistoryController.mjs b/services/web/app/src/Features/History/HistoryController.mjs
index 0961936356..d78ab0cb1a 100644
--- a/services/web/app/src/Features/History/HistoryController.mjs
+++ b/services/web/app/src/Features/History/HistoryController.mjs
@@ -24,7 +24,7 @@ import ProjectDetailsHandler from '../Project/ProjectDetailsHandler.mjs'
import ProjectEntityUpdateHandler from '../Project/ProjectEntityUpdateHandler.mjs'
import RestoreManager from './RestoreManager.mjs'
import { prepareZipAttachment } from '../../infrastructure/Response.js'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import { z, zz, validateReq } from '../../infrastructure/Validation.js'
// Number of seconds after which the browser should send a request to revalidate
diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs
index ec4005025b..c869eca186 100644
--- a/services/web/app/src/Features/Project/ProjectController.mjs
+++ b/services/web/app/src/Features/Project/ProjectController.mjs
@@ -21,14 +21,14 @@ import AuthorizationManager from '../Authorization/AuthorizationManager.mjs'
import InactiveProjectManager from '../InactiveData/InactiveProjectManager.mjs'
import ProjectUpdateHandler from './ProjectUpdateHandler.mjs'
import ProjectGetter from './ProjectGetter.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
import Sources from '../Authorization/Sources.mjs'
import TokenAccessHandler from '../TokenAccess/TokenAccessHandler.mjs'
import CollaboratorsGetter from '../Collaborators/CollaboratorsGetter.mjs'
import ProjectEntityHandler from './ProjectEntityHandler.mjs'
import TpdsProjectFlusher from '../ThirdPartyDataStore/TpdsProjectFlusher.mjs'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import BrandVariationsHandler from '../BrandVariations/BrandVariationsHandler.mjs'
import UserController from '../User/UserController.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
@@ -47,7 +47,7 @@ import UserUpdater from '../User/UserUpdater.mjs'
import Modules from '../../infrastructure/Modules.js'
import { z, zz, validateReq } from '../../infrastructure/Validation.js'
import UserGetter from '../User/UserGetter.mjs'
-import { isStandaloneAiAddOnPlanCode } from '../Subscription/AiHelper.js'
+import { isStandaloneAiAddOnPlanCode } from '../Subscription/AiHelper.mjs'
import SubscriptionController from '../Subscription/SubscriptionController.mjs'
import { formatCurrency } from '../../util/currency.js'
import UserSettingsHelper from './UserSettingsHelper.mjs'
diff --git a/services/web/app/src/Features/Project/ProjectCreationHandler.mjs b/services/web/app/src/Features/Project/ProjectCreationHandler.mjs
index 03ad4437a2..55df89bcc2 100644
--- a/services/web/app/src/Features/Project/ProjectCreationHandler.mjs
+++ b/services/web/app/src/Features/Project/ProjectCreationHandler.mjs
@@ -2,7 +2,7 @@ import OError from '@overleaf/o-error'
import metrics from '@overleaf/metrics'
import Settings from '@overleaf/settings'
import mongodb from 'mongodb-legacy'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import { Project } from '../../models/Project.mjs'
import { Folder } from '../../models/Folder.mjs'
import ProjectEntityUpdateHandler from './ProjectEntityUpdateHandler.mjs'
diff --git a/services/web/app/src/Features/Project/ProjectDetailsHandler.mjs b/services/web/app/src/Features/Project/ProjectDetailsHandler.mjs
index a2116ecdf3..7ef920c087 100644
--- a/services/web/app/src/Features/Project/ProjectDetailsHandler.mjs
+++ b/services/web/app/src/Features/Project/ProjectDetailsHandler.mjs
@@ -6,7 +6,7 @@ import logger from '@overleaf/logger'
import TpdsUpdateSender from '../ThirdPartyDataStore/TpdsUpdateSender.mjs'
import PublicAccessLevels from '../Authorization/PublicAccessLevels.mjs'
import Errors from '../Errors/Errors.js'
-import TokenGenerator from '../TokenGenerator/TokenGenerator.js'
+import TokenGenerator from '../TokenGenerator/TokenGenerator.mjs'
import ProjectHelper from './ProjectHelper.mjs'
import settings from '@overleaf/settings'
import { callbackify } from 'node:util'
diff --git a/services/web/app/src/Features/Project/ProjectGetter.mjs b/services/web/app/src/Features/Project/ProjectGetter.mjs
index 2aafb73892..77069a7d72 100644
--- a/services/web/app/src/Features/Project/ProjectGetter.mjs
+++ b/services/web/app/src/Features/Project/ProjectGetter.mjs
@@ -1,5 +1,5 @@
import { db } from '../../infrastructure/mongodb.js'
-import { normalizeQuery } from '../Helpers/Mongo.js'
+import Mongo from '../Helpers/Mongo.mjs'
import OError from '@overleaf/o-error'
import { Project } from '../../models/Project.mjs'
import LockManager from '../../infrastructure/LockManager.js'
@@ -8,6 +8,8 @@ import { callbackifyAll } from '@overleaf/promise-utils'
import ProjectEntityMongoUpdateHandler from './ProjectEntityMongoUpdateHandler.mjs'
import CollaboratorsGetter from '../Collaborators/CollaboratorsGetter.mjs'
+const { normalizeQuery } = Mongo
+
const ProjectGetter = {
EXCLUDE_DEPTH: 8,
diff --git a/services/web/app/src/Features/Project/ProjectListController.mjs b/services/web/app/src/Features/Project/ProjectListController.mjs
index dfb165f535..2acbb85c66 100644
--- a/services/web/app/src/Features/Project/ProjectListController.mjs
+++ b/services/web/app/src/Features/Project/ProjectListController.mjs
@@ -6,7 +6,7 @@ import Metrics from '@overleaf/metrics'
import Settings from '@overleaf/settings'
import ProjectHelper from './ProjectHelper.mjs'
import ProjectGetter from './ProjectGetter.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
import Sources from '../Authorization/Sources.mjs'
import UserGetter from '../User/UserGetter.mjs'
@@ -14,7 +14,7 @@ import SurveyHandler from '../Survey/SurveyHandler.mjs'
import TagsHandler from '../Tags/TagsHandler.mjs'
import { expressify } from '@overleaf/promise-utils'
import logger from '@overleaf/logger'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import SubscriptionViewModelBuilder from '../Subscription/SubscriptionViewModelBuilder.mjs'
import NotificationsHandler from '../Notifications/NotificationsHandler.mjs'
import Modules from '../../infrastructure/Modules.js'
diff --git a/services/web/app/src/Features/Security/LoginRateLimiter.js b/services/web/app/src/Features/Security/LoginRateLimiter.mjs
similarity index 78%
rename from services/web/app/src/Features/Security/LoginRateLimiter.js
rename to services/web/app/src/Features/Security/LoginRateLimiter.mjs
index bab5193632..f85f9be5f7 100644
--- a/services/web/app/src/Features/Security/LoginRateLimiter.js
+++ b/services/web/app/src/Features/Security/LoginRateLimiter.mjs
@@ -1,6 +1,6 @@
-const { RateLimiter } = require('../../infrastructure/RateLimiter')
-const { callbackify } = require('@overleaf/promise-utils')
-const Settings = require('@overleaf/settings')
+import { RateLimiter } from '../../infrastructure/RateLimiter.js'
+import { callbackify } from '@overleaf/promise-utils'
+import Settings from '@overleaf/settings'
const rateLimiterLoginEmail = new RateLimiter(
'login',
@@ -38,4 +38,4 @@ LoginRateLimiter.promises = {
recordSuccessfulLogin,
}
-module.exports = LoginRateLimiter
+export default LoginRateLimiter
diff --git a/services/web/app/src/Features/Security/RateLimiterMiddleware.mjs b/services/web/app/src/Features/Security/RateLimiterMiddleware.mjs
index c3b4de8bae..8119d6f74a 100644
--- a/services/web/app/src/Features/Security/RateLimiterMiddleware.mjs
+++ b/services/web/app/src/Features/Security/RateLimiterMiddleware.mjs
@@ -1,6 +1,6 @@
import logger from '@overleaf/logger'
import SessionManager from '../Authentication/SessionManager.mjs'
-import LoginRateLimiter from './LoginRateLimiter.js'
+import LoginRateLimiter from './LoginRateLimiter.mjs'
import settings from '@overleaf/settings'
/**
diff --git a/services/web/app/src/Features/SplitTests/SplitTestHandler.mjs b/services/web/app/src/Features/SplitTests/SplitTestHandler.mjs
index 36501f2be4..1a9a296c83 100644
--- a/services/web/app/src/Features/SplitTests/SplitTestHandler.mjs
+++ b/services/web/app/src/Features/SplitTests/SplitTestHandler.mjs
@@ -8,7 +8,7 @@ import { callbackify } from 'node:util'
import SplitTestCache from './SplitTestCache.mjs'
import { SplitTest } from '../../models/SplitTest.mjs'
import UserAnalyticsIdCache from '../Analytics/UserAnalyticsIdCache.mjs'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import SplitTestUtils from './SplitTestUtils.mjs'
import Settings from '@overleaf/settings'
import SessionManager from '../Authentication/SessionManager.mjs'
diff --git a/services/web/app/src/Features/StaticPages/HomeController.mjs b/services/web/app/src/Features/StaticPages/HomeController.mjs
index 740e1eb240..bd82075ae5 100644
--- a/services/web/app/src/Features/StaticPages/HomeController.mjs
+++ b/services/web/app/src/Features/StaticPages/HomeController.mjs
@@ -1,4 +1,4 @@
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
import Path from 'node:path'
import fs from 'node:fs'
diff --git a/services/web/app/src/Features/Subscription/AiHelper.js b/services/web/app/src/Features/Subscription/AiHelper.mjs
similarity index 78%
rename from services/web/app/src/Features/Subscription/AiHelper.js
rename to services/web/app/src/Features/Subscription/AiHelper.mjs
index d6b0e06644..37a1a2e4a7 100644
--- a/services/web/app/src/Features/Subscription/AiHelper.js
+++ b/services/web/app/src/Features/Subscription/AiHelper.mjs
@@ -2,9 +2,9 @@
// Initially, this functions lived in PaymentProviderEntities.js,
// but it was moved to this file to prevent circular dependency issue
-const AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE = 'assistant'
-const AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE = 'assistant-annual'
-const AI_ADD_ON_CODE = 'assistant'
+export const AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE = 'assistant'
+export const AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE = 'assistant-annual'
+export const AI_ADD_ON_CODE = 'assistant'
/**
* Returns whether the given plan code is a standalone AI plan
@@ -12,7 +12,7 @@ const AI_ADD_ON_CODE = 'assistant'
* @param {string | null | undefined} planCode
* @return {boolean}
*/
-function isStandaloneAiAddOnPlanCode(planCode) {
+export function isStandaloneAiAddOnPlanCode(planCode) {
return (
planCode === AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE ||
planCode === AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE
@@ -28,7 +28,7 @@ function isStandaloneAiAddOnPlanCode(planCode) {
*
* @return {boolean}
*/
-function subscriptionChangeIsAiAssistUpgrade(subscriptionChange) {
+export function subscriptionChangeIsAiAssistUpgrade(subscriptionChange) {
return Boolean(
isStandaloneAiAddOnPlanCode(subscriptionChange.nextPlanCode) ||
subscriptionChange.nextAddOns?.some(
@@ -37,7 +37,7 @@ function subscriptionChangeIsAiAssistUpgrade(subscriptionChange) {
)
}
-module.exports = {
+export default {
AI_ADD_ON_CODE,
AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE,
AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE,
diff --git a/services/web/app/src/Features/Subscription/Errors.js b/services/web/app/src/Features/Subscription/Errors.mjs
similarity index 57%
rename from services/web/app/src/Features/Subscription/Errors.js
rename to services/web/app/src/Features/Subscription/Errors.mjs
index 8d4498290a..546f5822e5 100644
--- a/services/web/app/src/Features/Subscription/Errors.js
+++ b/services/web/app/src/Features/Subscription/Errors.mjs
@@ -1,9 +1,10 @@
// @ts-check
-const Errors = require('../Errors/Errors')
-const OError = require('@overleaf/o-error')
+import Errors from '../Errors/Errors.js'
-class RecurlyTransactionError extends Errors.BackwardCompatibleError {
+import OError from '@overleaf/o-error'
+
+export class RecurlyTransactionError extends Errors.BackwardCompatibleError {
constructor(options) {
super({
message: 'Unknown transaction error',
@@ -12,27 +13,27 @@ class RecurlyTransactionError extends Errors.BackwardCompatibleError {
}
}
-class DuplicateAddOnError extends OError {}
+export class DuplicateAddOnError extends OError {}
-class AddOnNotPresentError extends OError {}
+export class AddOnNotPresentError extends OError {}
-class MissingBillingInfoError extends OError {}
+export class MissingBillingInfoError extends OError {}
-class ManuallyCollectedError extends OError {}
+export class ManuallyCollectedError extends OError {}
-class PendingChangeError extends OError {}
+export class PendingChangeError extends OError {}
-class InactiveError extends OError {}
+export class InactiveError extends OError {}
-class SubtotalLimitExceededError extends OError {}
+export class SubtotalLimitExceededError extends OError {}
-class HasPastDueInvoiceError extends OError {}
+export class HasPastDueInvoiceError extends OError {}
-class HasNoAdditionalLicenseWhenManuallyCollectedError extends OError {}
+export class HasNoAdditionalLicenseWhenManuallyCollectedError extends OError {}
-class InvalidTaxIdError extends OError {}
+export class InvalidTaxIdError extends OError {}
-class StripeClientIdempotencyKeyInUseError extends OError {
+export class StripeClientIdempotencyKeyInUseError extends OError {
constructor() {
super('Stripe idempotency key was already in use')
}
@@ -43,7 +44,7 @@ class StripeClientIdempotencyKeyInUseError extends OError {
* @property {string} PaymentActionRequiredInfo.clientSecret
* @property {string} PaymentActionRequiredInfo.publicKey
*/
-class PaymentActionRequiredError extends OError {
+export class PaymentActionRequiredError extends OError {
/**
* @param {PaymentActionRequiredInfo} info
*/
@@ -58,7 +59,7 @@ class PaymentActionRequiredError extends OError {
* @property {string} PaymentFailedInfo.reason
* @property {string} PaymentFailedInfo.adviceCode
*/
-class PaymentFailedError extends OError {
+export class PaymentFailedError extends OError {
/**
* @param {PaymentFailedInfo} info
*/
@@ -67,7 +68,7 @@ class PaymentFailedError extends OError {
}
}
-module.exports = {
+export default {
RecurlyTransactionError,
DuplicateAddOnError,
AddOnNotPresentError,
diff --git a/services/web/app/src/Features/Subscription/FeaturesUpdater.mjs b/services/web/app/src/Features/Subscription/FeaturesUpdater.mjs
index 9b318ba5d2..1304c5b35c 100644
--- a/services/web/app/src/Features/Subscription/FeaturesUpdater.mjs
+++ b/services/web/app/src/Features/Subscription/FeaturesUpdater.mjs
@@ -13,9 +13,9 @@ import V1SubscriptionManager from './V1SubscriptionManager.mjs'
import InstitutionsFeatures from '../Institutions/InstitutionsFeatures.mjs'
import UserGetter from '../User/UserGetter.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
-import Queues from '../../infrastructure/Queues.js'
+import Queues from '../../infrastructure/Queues.mjs'
import Modules from '../../infrastructure/Modules.js'
-import { AI_ADD_ON_CODE } from './AiHelper.js'
+import { AI_ADD_ON_CODE } from './AiHelper.mjs'
/**
* Enqueue a job for refreshing features for the given user
diff --git a/services/web/app/src/Features/Subscription/GroupPlansData.js b/services/web/app/src/Features/Subscription/GroupPlansData.mjs
similarity index 92%
rename from services/web/app/src/Features/Subscription/GroupPlansData.js
rename to services/web/app/src/Features/Subscription/GroupPlansData.mjs
index 5f765056be..5fa0041773 100644
--- a/services/web/app/src/Features/Subscription/GroupPlansData.js
+++ b/services/web/app/src/Features/Subscription/GroupPlansData.mjs
@@ -1,6 +1,6 @@
-const Settings = require('@overleaf/settings')
-const fs = require('fs')
-const Path = require('path')
+import Settings from '@overleaf/settings'
+import fs from 'node:fs'
+import Path from 'node:path'
// The groups.json file encodes the various group plan options we provide, and
// is used in the app the render the appropriate dialog in the plans page, and
@@ -12,7 +12,7 @@ const Path = require('path')
// fetch pricing data and generate a groups.json using the current Recurly
// prices
const data = fs.readFileSync(
- Path.join(__dirname, '/../../../templates/plans/groups.json')
+ Path.join(import.meta.dirname, '/../../../templates/plans/groups.json')
)
const groups = JSON.parse(data.toString())
@@ -63,4 +63,4 @@ for (const [usage, planData] of Object.entries(groups)) {
}
}
-module.exports = groups
+export default groups
diff --git a/services/web/app/src/Features/Subscription/LimitationsManager.mjs b/services/web/app/src/Features/Subscription/LimitationsManager.mjs
index 81296d72b8..bd33ee4234 100644
--- a/services/web/app/src/Features/Subscription/LimitationsManager.mjs
+++ b/services/web/app/src/Features/Subscription/LimitationsManager.mjs
@@ -8,7 +8,7 @@ import SubscriptionLocator from './SubscriptionLocator.mjs'
import Settings from '@overleaf/settings'
import CollaboratorsGetter from '../Collaborators/CollaboratorsGetter.mjs'
import CollaboratorsInvitesGetter from '../Collaborators/CollaboratorsInviteGetter.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import { callbackify, callbackifyMultiResult } from '@overleaf/promise-utils'
async function allowedNumberOfCollaboratorsInProject(projectId) {
diff --git a/services/web/app/src/Features/Subscription/PaymentProviderEntities.mjs b/services/web/app/src/Features/Subscription/PaymentProviderEntities.mjs
index caeab041c1..2858f593fc 100644
--- a/services/web/app/src/Features/Subscription/PaymentProviderEntities.mjs
+++ b/services/web/app/src/Features/Subscription/PaymentProviderEntities.mjs
@@ -18,10 +18,10 @@
import OError from '@overleaf/o-error'
-import { DuplicateAddOnError, AddOnNotPresentError } from './Errors.js'
+import { DuplicateAddOnError, AddOnNotPresentError } from './Errors.mjs'
import PlansLocator from './PlansLocator.mjs'
import SubscriptionHelper from './SubscriptionHelper.mjs'
-import { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } from './AiHelper.js'
+import { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } from './AiHelper.mjs'
export const MEMBERS_LIMIT_ADD_ON_CODE = 'additional-license'
export class PaymentProviderSubscription {
diff --git a/services/web/app/src/Features/Subscription/RecurlyClient.mjs b/services/web/app/src/Features/Subscription/RecurlyClient.mjs
index d983fcfa8c..cfd4c9443a 100644
--- a/services/web/app/src/Features/Subscription/RecurlyClient.mjs
+++ b/services/web/app/src/Features/Subscription/RecurlyClient.mjs
@@ -22,9 +22,9 @@ import {
import {
MissingBillingInfoError,
SubtotalLimitExceededError,
-} from './Errors.js'
+} from './Errors.mjs'
import RecurlyMetrics from './RecurlyMetrics.mjs'
-import { isStandaloneAiAddOnPlanCode, AI_ADD_ON_CODE } from './AiHelper.js'
+import { isStandaloneAiAddOnPlanCode, AI_ADD_ON_CODE } from './AiHelper.mjs'
/**
* @import { PaymentProviderSubscriptionChangeRequest } from './PaymentProviderEntities.mjs'
diff --git a/services/web/app/src/Features/Subscription/RecurlyEventHandler.mjs b/services/web/app/src/Features/Subscription/RecurlyEventHandler.mjs
index ac718fbc4e..403456fc61 100644
--- a/services/web/app/src/Features/Subscription/RecurlyEventHandler.mjs
+++ b/services/web/app/src/Features/Subscription/RecurlyEventHandler.mjs
@@ -1,7 +1,7 @@
import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
import SubscriptionEmailHandler from './SubscriptionEmailHandler.mjs'
-import { AI_ADD_ON_CODE } from './AiHelper.js'
+import { AI_ADD_ON_CODE } from './AiHelper.mjs'
import mongodb from 'mongodb-legacy'
const { ObjectId } = mongodb
diff --git a/services/web/app/src/Features/Subscription/RecurlyWrapper.mjs b/services/web/app/src/Features/Subscription/RecurlyWrapper.mjs
index e44d930411..e644953d31 100644
--- a/services/web/app/src/Features/Subscription/RecurlyWrapper.mjs
+++ b/services/web/app/src/Features/Subscription/RecurlyWrapper.mjs
@@ -7,7 +7,7 @@ import Settings from '@overleaf/settings'
import xml2js from 'xml2js'
import logger from '@overleaf/logger'
import Errors from '../Errors/Errors.js'
-import SubscriptionErrors from './Errors.js'
+import SubscriptionErrors from './Errors.mjs'
import { callbackify } from '@overleaf/promise-utils'
import RecurlyMetrics from './RecurlyMetrics.mjs'
diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.mjs b/services/web/app/src/Features/Subscription/SubscriptionController.mjs
index 0108a9e348..e85c7fd351 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionController.mjs
+++ b/services/web/app/src/Features/Subscription/SubscriptionController.mjs
@@ -10,13 +10,13 @@ import Settings from '@overleaf/settings'
import logger from '@overleaf/logger'
import GeoIpLookup from '../../infrastructure/GeoIpLookup.mjs'
import FeaturesUpdater from './FeaturesUpdater.mjs'
-import GroupPlansData from './GroupPlansData.js'
+import GroupPlansData from './GroupPlansData.mjs'
import V1SubscriptionManager from './V1SubscriptionManager.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
import RecurlyEventHandler from './RecurlyEventHandler.mjs'
import { expressify } from '@overleaf/promise-utils'
import OError from '@overleaf/o-error'
-import Errors from './Errors.js'
+import Errors from './Errors.mjs'
import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
import AuthorizationManager from '../Authorization/AuthorizationManager.mjs'
import Modules from '../../infrastructure/Modules.js'
@@ -26,7 +26,7 @@ import RecurlyClient from './RecurlyClient.mjs'
import {
AI_ADD_ON_CODE,
subscriptionChangeIsAiAssistUpgrade,
-} from './AiHelper.js'
+} from './AiHelper.mjs'
import PlansLocator from './PlansLocator.mjs'
import { User } from '../../models/User.mjs'
import UserGetter from '../User/UserGetter.mjs'
diff --git a/services/web/app/src/Features/Subscription/SubscriptionEmailBuilder.mjs b/services/web/app/src/Features/Subscription/SubscriptionEmailBuilder.mjs
index 9e7552d207..c24bb6779c 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionEmailBuilder.mjs
+++ b/services/web/app/src/Features/Subscription/SubscriptionEmailBuilder.mjs
@@ -1,5 +1,5 @@
import EmailBuilder from '../Email/EmailBuilder.mjs'
-import EmailMessageHelper from '../Email/EmailMessageHelper.js'
+import EmailMessageHelper from '../Email/EmailMessageHelper.mjs'
import settings from '@overleaf/settings'
EmailBuilder.templates.trialOnboarding = EmailBuilder.NoCTAEmailTemplate({
diff --git a/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs b/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs
index f9fe532391..746274c161 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs
+++ b/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs
@@ -20,7 +20,7 @@ import {
HasPastDueInvoiceError,
HasNoAdditionalLicenseWhenManuallyCollectedError,
PaymentActionRequiredError,
-} from './Errors.js'
+} from './Errors.mjs'
const MAX_NUMBER_OF_USERS = 20
const MAX_NUMBER_OF_PO_NUMBER_CHARACTERS = 50
diff --git a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.mjs b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.mjs
index 3a14f74cc0..facdf16a13 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.mjs
+++ b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.mjs
@@ -9,7 +9,7 @@ import { Subscription } from '../../models/Subscription.mjs'
import { User } from '../../models/User.mjs'
import PlansLocator from './PlansLocator.mjs'
import TeamInvitesHandler from './TeamInvitesHandler.mjs'
-import GroupPlansData from './GroupPlansData.js'
+import GroupPlansData from './GroupPlansData.mjs'
import Modules from '../../infrastructure/Modules.js'
import PaymentProviderEntities from './PaymentProviderEntities.mjs'
import {
@@ -18,7 +18,7 @@ import {
InactiveError,
HasPastDueInvoiceError,
HasNoAdditionalLicenseWhenManuallyCollectedError,
-} from './Errors.js'
+} from './Errors.mjs'
import EmailHelper from '../Helpers/EmailHelper.mjs'
import { InvalidEmailError } from '../Errors/Errors.js'
diff --git a/services/web/app/src/Features/Subscription/SubscriptionHelper.mjs b/services/web/app/src/Features/Subscription/SubscriptionHelper.mjs
index 0a84b28520..ca67df6e15 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionHelper.mjs
+++ b/services/web/app/src/Features/Subscription/SubscriptionHelper.mjs
@@ -2,8 +2,8 @@
import Settings from '@overleaf/settings'
import { formatCurrency } from '../../util/currency.js'
-import GroupPlansData from './GroupPlansData.js'
-import { isStandaloneAiAddOnPlanCode } from './AiHelper.js'
+import GroupPlansData from './GroupPlansData.mjs'
+import { isStandaloneAiAddOnPlanCode } from './AiHelper.mjs'
import { Subscription } from '../../models/Subscription.mjs'
const MILLISECONDS = 1_000
diff --git a/services/web/app/src/Features/Subscription/SubscriptionLocator.mjs b/services/web/app/src/Features/Subscription/SubscriptionLocator.mjs
index 3ded2be604..1417b1be1a 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionLocator.mjs
+++ b/services/web/app/src/Features/Subscription/SubscriptionLocator.mjs
@@ -8,8 +8,8 @@ import { Subscription } from '../../models/Subscription.mjs'
import SubscriptionHelper from './SubscriptionHelper.mjs'
import { DeletedSubscription } from '../../models/DeletedSubscription.mjs'
import logger from '@overleaf/logger'
-import { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } from './AiHelper.js'
-import './GroupPlansData.js' // make sure dynamic group plans are loaded
+import { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } from './AiHelper.mjs'
+import './GroupPlansData.mjs' // make sure dynamic group plans are loaded
const SubscriptionLocator = {
async getUsersSubscription(userOrId) {
diff --git a/services/web/app/src/Features/Subscription/SubscriptionUpdater.mjs b/services/web/app/src/Features/Subscription/SubscriptionUpdater.mjs
index 6936a55634..f0a0ef7ec2 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionUpdater.mjs
+++ b/services/web/app/src/Features/Subscription/SubscriptionUpdater.mjs
@@ -8,7 +8,7 @@ import FeaturesHelper from './FeaturesHelper.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
import { DeletedSubscription } from '../../models/DeletedSubscription.mjs'
import logger from '@overleaf/logger'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import UserAuditLogHandler from '../User/UserAuditLogHandler.mjs'
import UserUpdater from '../User/UserUpdater.mjs'
import AccountMappingHelper from '../Analytics/AccountMappingHelper.mjs'
diff --git a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.mjs b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.mjs
index b4e67c67e8..b573a4fad3 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.mjs
+++ b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.mjs
@@ -2,7 +2,7 @@
import Settings from '@overleaf/settings'
import PlansLocator from './PlansLocator.mjs'
-import { isStandaloneAiAddOnPlanCode } from './AiHelper.js'
+import { isStandaloneAiAddOnPlanCode } from './AiHelper.mjs'
import PaymentProviderEntities from './PaymentProviderEntities.mjs'
import SubscriptionFormatters from './SubscriptionFormatters.mjs'
import SubscriptionLocator from './SubscriptionLocator.mjs'
diff --git a/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs b/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs
index 608b567f3d..16d627e128 100644
--- a/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs
+++ b/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs
@@ -6,7 +6,7 @@ import logger from '@overleaf/logger'
import OError from '@overleaf/o-error'
import { expressify } from '@overleaf/promise-utils'
import AuthorizationManager from '../Authorization/AuthorizationManager.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.mjs'
import CollaboratorsInviteHandler from '../Collaborators/CollaboratorsInviteHandler.mjs'
import CollaboratorsHandler from '../Collaborators/CollaboratorsHandler.mjs'
diff --git a/services/web/app/src/Features/TokenAccess/TokenAccessHandler.mjs b/services/web/app/src/Features/TokenAccess/TokenAccessHandler.mjs
index 88d643707f..5d0c004dfc 100644
--- a/services/web/app/src/Features/TokenAccess/TokenAccessHandler.mjs
+++ b/services/web/app/src/Features/TokenAccess/TokenAccessHandler.mjs
@@ -1,15 +1,15 @@
import { Project } from '../../models/Project.mjs'
import PublicAccessLevels from '../Authorization/PublicAccessLevels.mjs'
-import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import mongodb from 'mongodb-legacy'
import Metrics from '@overleaf/metrics'
import Settings from '@overleaf/settings'
import logger from '@overleaf/logger'
-import V1Api from '../V1/V1Api.js'
+import V1Api from '../V1/V1Api.mjs'
import crypto from 'node:crypto'
import { callbackifyAll } from '@overleaf/promise-utils'
import Analytics from '../Analytics/AnalyticsManager.mjs'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
const { ObjectId } = mongodb
diff --git a/services/web/app/src/Features/TokenGenerator/TokenGenerator.js b/services/web/app/src/Features/TokenGenerator/TokenGenerator.mjs
similarity index 92%
rename from services/web/app/src/Features/TokenGenerator/TokenGenerator.js
rename to services/web/app/src/Features/TokenGenerator/TokenGenerator.mjs
index e3a321028b..4a07feb1b6 100644
--- a/services/web/app/src/Features/TokenGenerator/TokenGenerator.js
+++ b/services/web/app/src/Features/TokenGenerator/TokenGenerator.mjs
@@ -1,7 +1,7 @@
-const crypto = require('crypto')
-const V1Api = require('../V1/V1Api')
-const Features = require('../../infrastructure/Features')
-const { callbackify } = require('util')
+import crypto from 'node:crypto'
+import V1Api from '../V1/V1Api.mjs'
+import Features from '../../infrastructure/Features.mjs'
+import { callbackify } from 'node:util'
// (From Overleaf `random_token.rb`)
// Letters (not numbers! see generate_token) used in tokens. They're all
@@ -94,4 +94,4 @@ const TokenGenerator = {
TokenGenerator.promises = {
generateUniqueReadOnlyToken,
}
-module.exports = TokenGenerator
+export default TokenGenerator
diff --git a/services/web/app/src/Features/Uploads/ArchiveErrors.js b/services/web/app/src/Features/Uploads/ArchiveErrors.mjs
similarity index 59%
rename from services/web/app/src/Features/Uploads/ArchiveErrors.js
rename to services/web/app/src/Features/Uploads/ArchiveErrors.mjs
index d78941ecce..38a6c31e41 100644
--- a/services/web/app/src/Features/Uploads/ArchiveErrors.js
+++ b/services/web/app/src/Features/Uploads/ArchiveErrors.mjs
@@ -1,6 +1,6 @@
-const Errors = require('../Errors/Errors')
+import Errors from '../Errors/Errors.js'
-class InvalidZipFileError extends Errors.BackwardCompatibleError {
+export class InvalidZipFileError extends Errors.BackwardCompatibleError {
constructor(options) {
super({
message: 'invalid_zip_file',
@@ -9,7 +9,7 @@ class InvalidZipFileError extends Errors.BackwardCompatibleError {
}
}
-class EmptyZipFileError extends InvalidZipFileError {
+export class EmptyZipFileError extends InvalidZipFileError {
constructor(options) {
super({
message: 'empty_zip_file',
@@ -18,7 +18,7 @@ class EmptyZipFileError extends InvalidZipFileError {
}
}
-class ZipContentsTooLargeError extends InvalidZipFileError {
+export class ZipContentsTooLargeError extends InvalidZipFileError {
constructor(options) {
super({
message: 'zip_contents_too_large',
@@ -27,7 +27,7 @@ class ZipContentsTooLargeError extends InvalidZipFileError {
}
}
-module.exports = {
+export default {
InvalidZipFileError,
EmptyZipFileError,
ZipContentsTooLargeError,
diff --git a/services/web/app/src/Features/Uploads/ArchiveManager.mjs b/services/web/app/src/Features/Uploads/ArchiveManager.mjs
index 6c3c470c85..24eac59f8b 100644
--- a/services/web/app/src/Features/Uploads/ArchiveManager.mjs
+++ b/services/web/app/src/Features/Uploads/ArchiveManager.mjs
@@ -24,7 +24,7 @@ import {
InvalidZipFileError,
EmptyZipFileError,
ZipContentsTooLargeError,
-} from './ArchiveErrors.js'
+} from './ArchiveErrors.mjs'
import _ from 'lodash'
import { promisifyAll } from '@overleaf/promise-utils'
diff --git a/services/web/app/src/Features/Uploads/ProjectUploadController.mjs b/services/web/app/src/Features/Uploads/ProjectUploadController.mjs
index e88b9944d5..61bf91b16b 100644
--- a/services/web/app/src/Features/Uploads/ProjectUploadController.mjs
+++ b/services/web/app/src/Features/Uploads/ProjectUploadController.mjs
@@ -8,7 +8,7 @@ import SessionManager from '../Authentication/SessionManager.mjs'
import EditorController from '../Editor/EditorController.mjs'
import ProjectLocator from '../Project/ProjectLocator.mjs'
import Settings from '@overleaf/settings'
-import { InvalidZipFileError } from './ArchiveErrors.js'
+import { InvalidZipFileError } from './ArchiveErrors.mjs'
import multer from 'multer'
import lodash from 'lodash'
import { expressify } from '@overleaf/promise-utils'
diff --git a/services/web/app/src/Features/User/UserController.mjs b/services/web/app/src/Features/User/UserController.mjs
index 9a21c494d1..88b09691d7 100644
--- a/services/web/app/src/Features/User/UserController.mjs
+++ b/services/web/app/src/Features/User/UserController.mjs
@@ -7,7 +7,7 @@ import logger from '@overleaf/logger'
import metrics from '@overleaf/metrics'
import AuthenticationManager from '../Authentication/AuthenticationManager.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import { z, validateReq } from '../../infrastructure/Validation.js'
import UserAuditLogHandler from './UserAuditLogHandler.mjs'
import UserSessionsManager from './UserSessionsManager.mjs'
diff --git a/services/web/app/src/Features/User/UserCreator.mjs b/services/web/app/src/Features/User/UserCreator.mjs
index 08ed98b455..d6e5aa8f57 100644
--- a/services/web/app/src/Features/User/UserCreator.mjs
+++ b/services/web/app/src/Features/User/UserCreator.mjs
@@ -1,7 +1,7 @@
import logger from '@overleaf/logger'
import util from 'node:util'
import { AffiliationError } from '../Errors/Errors.js'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import { User } from '../../models/User.mjs'
import UserDeleter from './UserDeleter.mjs'
import UserGetter from './UserGetter.mjs'
diff --git a/services/web/app/src/Features/User/UserEmailsController.mjs b/services/web/app/src/Features/User/UserEmailsController.mjs
index 0a30410740..008bc62030 100644
--- a/services/web/app/src/Features/User/UserEmailsController.mjs
+++ b/services/web/app/src/Features/User/UserEmailsController.mjs
@@ -17,7 +17,7 @@ import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
import UserPrimaryEmailCheckHandler from '../User/UserPrimaryEmailCheckHandler.mjs'
import UserAuditLogHandler from './UserAuditLogHandler.mjs'
import { RateLimiter } from '../../infrastructure/RateLimiter.js'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import tsscmp from 'tsscmp'
import Modules from '../../infrastructure/Modules.js'
diff --git a/services/web/app/src/Features/User/UserGetter.mjs b/services/web/app/src/Features/User/UserGetter.mjs
index c8bce40136..c2459c35dc 100644
--- a/services/web/app/src/Features/User/UserGetter.mjs
+++ b/services/web/app/src/Features/User/UserGetter.mjs
@@ -5,13 +5,14 @@ import settings from '@overleaf/settings'
import InstitutionsAPI from '../Institutions/InstitutionsAPI.mjs'
import InstitutionsHelper from '../Institutions/InstitutionsHelper.mjs'
import Errors from '../Errors/Errors.js'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import { User } from '../../models/User.mjs'
-import { normalizeQuery, normalizeMultiQuery } from '../Helpers/Mongo.js'
+import Mongo from '../Helpers/Mongo.mjs'
import Modules from '../../infrastructure/Modules.js'
import FeaturesHelper from '../Subscription/FeaturesHelper.mjs'
import AsyncLocalStorage from '../../infrastructure/AsyncLocalStorage.js'
+const { normalizeQuery, normalizeMultiQuery } = Mongo
const InstitutionsAPIPromises = InstitutionsAPI.promises
function _lastDayToReconfirm(emailData, institutionData) {
diff --git a/services/web/app/src/Features/User/UserOnboardingEmailManager.mjs b/services/web/app/src/Features/User/UserOnboardingEmailManager.mjs
index b29bbd2d96..fd10cf13d8 100644
--- a/services/web/app/src/Features/User/UserOnboardingEmailManager.mjs
+++ b/services/web/app/src/Features/User/UserOnboardingEmailManager.mjs
@@ -1,4 +1,4 @@
-import Queues from '../../infrastructure/Queues.js'
+import Queues from '../../infrastructure/Queues.mjs'
import EmailHandler from '../Email/EmailHandler.mjs'
import UserUpdater from './UserUpdater.mjs'
import UserGetter from './UserGetter.mjs'
diff --git a/services/web/app/src/Features/User/UserPagesController.mjs b/services/web/app/src/Features/User/UserPagesController.mjs
index d2e669a733..42d7167b9d 100644
--- a/services/web/app/src/Features/User/UserPagesController.mjs
+++ b/services/web/app/src/Features/User/UserPagesController.mjs
@@ -9,7 +9,7 @@ import NewsletterManager from '../Newsletter/NewsletterManager.mjs'
import SubscriptionLocator from '../Subscription/SubscriptionLocator.mjs'
import _ from 'lodash'
import { expressify } from '@overleaf/promise-utils'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import Modules from '../../infrastructure/Modules.js'
async function settingsPage(req, res) {
diff --git a/services/web/app/src/Features/User/UserPostRegistrationAnalyticsManager.mjs b/services/web/app/src/Features/User/UserPostRegistrationAnalyticsManager.mjs
index a21b929bd2..a1c55f9fe2 100644
--- a/services/web/app/src/Features/User/UserPostRegistrationAnalyticsManager.mjs
+++ b/services/web/app/src/Features/User/UserPostRegistrationAnalyticsManager.mjs
@@ -1,4 +1,4 @@
-import Queues from '../../infrastructure/Queues.js'
+import Queues from '../../infrastructure/Queues.mjs'
import UserGetter from './UserGetter.mjs'
import InstitutionsAPI from '../Institutions/InstitutionsAPI.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
diff --git a/services/web/app/src/Features/User/UserUpdater.mjs b/services/web/app/src/Features/User/UserUpdater.mjs
index f45b0456d2..4f4cdf2ba7 100644
--- a/services/web/app/src/Features/User/UserUpdater.mjs
+++ b/services/web/app/src/Features/User/UserUpdater.mjs
@@ -1,11 +1,11 @@
import logger from '@overleaf/logger'
import OError from '@overleaf/o-error'
import { db } from '../../infrastructure/mongodb.js'
-import { normalizeQuery } from '../Helpers/Mongo.js'
+import Mongo from '../Helpers/Mongo.mjs'
import { callbackify } from 'node:util'
import UserGetter from './UserGetter.mjs'
import InstitutionsAPI from '../Institutions/InstitutionsAPI.mjs'
-import Features from '../../infrastructure/Features.js'
+import Features from '../../infrastructure/Features.mjs'
import FeaturesUpdater from '../Subscription/FeaturesUpdater.mjs'
import EmailHandler from '../Email/EmailHandler.mjs'
import EmailHelper from '../Helpers/EmailHelper.mjs'
@@ -22,6 +22,8 @@ import UserSessionsManager from './UserSessionsManager.mjs'
import ThirdPartyIdentityManager from './ThirdPartyIdentityManager.mjs'
import AsyncLocalStorage from '../../infrastructure/AsyncLocalStorage.js'
+const { normalizeQuery } = Mongo
+
async function _sendSecurityAlertPrimaryEmailChanged(
userId,
oldEmail,
diff --git a/services/web/app/src/Features/UserMembership/UserMembershipViewModel.mjs b/services/web/app/src/Features/UserMembership/UserMembershipViewModel.mjs
index 5a34288ce1..c317940263 100644
--- a/services/web/app/src/Features/UserMembership/UserMembershipViewModel.mjs
+++ b/services/web/app/src/Features/UserMembership/UserMembershipViewModel.mjs
@@ -1,5 +1,7 @@
import UserGetter from '../User/UserGetter.mjs'
-import { isObjectIdInstance } from '../Helpers/Mongo.js'
+import Mongo from '../Helpers/Mongo.mjs'
+
+const { isObjectIdInstance } = Mongo
const UserMembershipViewModel = {
build(userOrEmail) {
diff --git a/services/web/app/src/Features/V1/V1Api.js b/services/web/app/src/Features/V1/V1Api.mjs
similarity index 93%
rename from services/web/app/src/Features/V1/V1Api.js
rename to services/web/app/src/Features/V1/V1Api.mjs
index ce15891a76..a81719b3c3 100644
--- a/services/web/app/src/Features/V1/V1Api.js
+++ b/services/web/app/src/Features/V1/V1Api.mjs
@@ -7,10 +7,11 @@
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
-const request = require('request')
-const settings = require('@overleaf/settings')
-const Errors = require('../Errors/Errors')
-const { promisifyAll } = require('@overleaf/promise-utils')
+import request from 'request'
+
+import settings from '@overleaf/settings'
+import Errors from '../Errors/Errors.js'
+import { promisifyAll } from '@overleaf/promise-utils'
// TODO: check what happens when these settings aren't defined
const DEFAULT_V1_PARAMS = {
@@ -103,4 +104,4 @@ V1Api.promises = promisifyAll(V1Api, {
oauthRequest: ['response', 'body'],
},
})
-module.exports = V1Api
+export default V1Api
diff --git a/services/web/app/src/Features/V1/V1Handler.mjs b/services/web/app/src/Features/V1/V1Handler.mjs
index 7c5f2d39ca..28aa86f3c0 100644
--- a/services/web/app/src/Features/V1/V1Handler.mjs
+++ b/services/web/app/src/Features/V1/V1Handler.mjs
@@ -11,7 +11,7 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import OError from '@overleaf/o-error'
-import V1Api from './V1Api.js'
+import V1Api from './V1Api.mjs'
import logger from '@overleaf/logger'
let V1Handler
diff --git a/services/web/app/src/infrastructure/ExpressLocals.mjs b/services/web/app/src/infrastructure/ExpressLocals.mjs
index 05281be6e2..b7c32faed4 100644
--- a/services/web/app/src/infrastructure/ExpressLocals.mjs
+++ b/services/web/app/src/infrastructure/ExpressLocals.mjs
@@ -6,7 +6,7 @@ import Path from 'node:path'
import moment from 'moment'
import { fetchJson } from '@overleaf/fetch-utils'
import contentDisposition from 'content-disposition'
-import Features from './Features.js'
+import Features from './Features.mjs'
import SessionManager from '../Features/Authentication/SessionManager.mjs'
import PackageVersions from './PackageVersions.js'
import Modules from './Modules.js'
diff --git a/services/web/app/src/infrastructure/Features.js b/services/web/app/src/infrastructure/Features.mjs
similarity index 96%
rename from services/web/app/src/infrastructure/Features.js
rename to services/web/app/src/infrastructure/Features.mjs
index c93d1f6702..b6b9961962 100644
--- a/services/web/app/src/infrastructure/Features.js
+++ b/services/web/app/src/infrastructure/Features.mjs
@@ -1,5 +1,5 @@
-const _ = require('lodash')
-const Settings = require('@overleaf/settings')
+import _ from 'lodash'
+import Settings from '@overleaf/settings'
const supportModuleAvailable = Settings.moduleImportSequence.includes('support')
@@ -98,4 +98,4 @@ const Features = {
},
}
-module.exports = Features
+export default Features
diff --git a/services/web/app/src/infrastructure/QueueWorkers.mjs b/services/web/app/src/infrastructure/QueueWorkers.mjs
index 134913a9df..77540e3d0f 100644
--- a/services/web/app/src/infrastructure/QueueWorkers.mjs
+++ b/services/web/app/src/infrastructure/QueueWorkers.mjs
@@ -1,5 +1,5 @@
-import Features from './Features.js'
-import Queues from './Queues.js'
+import Features from './Features.mjs'
+import Queues from './Queues.mjs'
import UserOnboardingEmailManager from '../Features/User/UserOnboardingEmailManager.mjs'
import UserPostRegistrationAnalyticsManager from '../Features/User/UserPostRegistrationAnalyticsManager.mjs'
import FeaturesUpdater from '../Features/Subscription/FeaturesUpdater.mjs'
diff --git a/services/web/app/src/infrastructure/Queues.js b/services/web/app/src/infrastructure/Queues.mjs
similarity index 93%
rename from services/web/app/src/infrastructure/Queues.js
rename to services/web/app/src/infrastructure/Queues.mjs
index 973e52420c..a5555a30c8 100644
--- a/services/web/app/src/infrastructure/Queues.js
+++ b/services/web/app/src/infrastructure/Queues.mjs
@@ -1,7 +1,7 @@
-const Queue = require('bull')
-const Settings = require('@overleaf/settings')
-const Features = require('../infrastructure/Features')
-const { addConnectionDrainer } = require('./GracefulShutdown')
+import Queue from 'bull'
+import Settings from '@overleaf/settings'
+import Features from '../infrastructure/Features.mjs'
+import { addConnectionDrainer } from './GracefulShutdown.js'
// Bull will keep a fixed number of the most recently completed jobs. This is
// useful to inspect recently completed jobs. The bull prometheus exporter also
@@ -127,7 +127,7 @@ async function createScheduledJob(queueName, { name, data, options }, delay) {
)
}
-module.exports = {
+export default {
getQueue,
createScheduledJob,
}
diff --git a/services/web/app/src/infrastructure/Server.mjs b/services/web/app/src/infrastructure/Server.mjs
index c95b3dabff..52044bf79a 100644
--- a/services/web/app/src/infrastructure/Server.mjs
+++ b/services/web/app/src/infrastructure/Server.mjs
@@ -23,7 +23,7 @@ import ReferalConnect from '../Features/Referal/ReferalConnect.mjs'
import RedirectManager from './RedirectManager.mjs'
import translations from './Translations.mjs'
import Views from './Views.js'
-import Features from './Features.js'
+import Features from './Features.mjs'
import ErrorController from '../Features/Errors/ErrorController.mjs'
import HttpErrorHandler from '../Features/Errors/HttpErrorHandler.mjs'
import UserSessionsManager from '../Features/User/UserSessionsManager.mjs'
diff --git a/services/web/app/src/infrastructure/SiteAdminHandler.mjs b/services/web/app/src/infrastructure/SiteAdminHandler.mjs
index 27cc1d9e39..f9368f485f 100644
--- a/services/web/app/src/infrastructure/SiteAdminHandler.mjs
+++ b/services/web/app/src/infrastructure/SiteAdminHandler.mjs
@@ -7,7 +7,7 @@ import {
addRequiredCleanupHandlerBeforeDrainingConnections,
} from './GracefulShutdown.js'
-import Features from './Features.js'
+import Features from './Features.mjs'
import UserHandler from '../Features/User/UserHandler.mjs'
import metrics from '@overleaf/metrics'
diff --git a/services/web/app/src/models/User.mjs b/services/web/app/src/models/User.mjs
index 5739474106..7ae68ca21f 100644
--- a/services/web/app/src/models/User.mjs
+++ b/services/web/app/src/models/User.mjs
@@ -1,6 +1,6 @@
import Settings from '@overleaf/settings'
import mongoose from '../infrastructure/Mongoose.js'
-import TokenGenerator from '../Features/TokenGenerator/TokenGenerator.js'
+import TokenGenerator from '../Features/TokenGenerator/TokenGenerator.mjs'
const { Schema } = mongoose
const { ObjectId } = Schema
diff --git a/services/web/app/src/router.mjs b/services/web/app/src/router.mjs
index 52c215ef13..d4af39c7e6 100644
--- a/services/web/app/src/router.mjs
+++ b/services/web/app/src/router.mjs
@@ -1,6 +1,6 @@
import AdminController from './Features/ServerAdmin/AdminController.mjs'
import ErrorController from './Features/Errors/ErrorController.mjs'
-import Features from './infrastructure/Features.js'
+import Features from './infrastructure/Features.mjs'
import ProjectController from './Features/Project/ProjectController.mjs'
import ProjectApiController from './Features/Project/ProjectApiController.mjs'
import ProjectListController from './Features/Project/ProjectListController.mjs'
diff --git a/services/web/scripts/backfill_mixpanel_user_properties.mjs b/services/web/scripts/backfill_mixpanel_user_properties.mjs
index 6e3dabd4c2..ab7259fb45 100644
--- a/services/web/scripts/backfill_mixpanel_user_properties.mjs
+++ b/services/web/scripts/backfill_mixpanel_user_properties.mjs
@@ -2,12 +2,13 @@
import '../app/src/models/User.mjs'
import { batchedUpdateWithResultHandling } from '@overleaf/mongo-utils/batchedUpdate.js'
import { promiseMapWithLimit } from '@overleaf/promise-utils'
-import { getQueue } from '../app/src/infrastructure/Queues.js'
+import Queues from '../app/src/infrastructure/Queues.mjs'
import SubscriptionLocator from '../app/src/Features/Subscription/SubscriptionLocator.mjs'
import PlansLocator from '../app/src/Features/Subscription/PlansLocator.mjs'
import FeaturesHelper from '../app/src/Features/Subscription/FeaturesHelper.mjs'
import { db } from '../app/src/infrastructure/mongodb.js'
+const { getQueue } = Queues
const WRITE_CONCURRENCY = parseInt(process.env.WRITE_CONCURRENCY || '10', 10)
const mixpanelSinkQueue = getQueue('analytics-mixpanel-sink')
diff --git a/services/web/scripts/regenerate_duplicate_referral_ids.mjs b/services/web/scripts/regenerate_duplicate_referral_ids.mjs
index 499823a08f..84f3a7f61e 100644
--- a/services/web/scripts/regenerate_duplicate_referral_ids.mjs
+++ b/services/web/scripts/regenerate_duplicate_referral_ids.mjs
@@ -3,7 +3,7 @@ import {
READ_PREFERENCE_SECONDARY,
} from '../app/src/infrastructure/mongodb.js'
import { promiseMapWithLimit } from '@overleaf/promise-utils'
-import TokenGenerator from '../app/src/Features/TokenGenerator/TokenGenerator.js'
+import TokenGenerator from '../app/src/Features/TokenGenerator/TokenGenerator.mjs'
import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
const VERBOSE_LOGGING = process.env.VERBOSE_LOGGING === 'true'
diff --git a/services/web/test/acceptance/src/ActiveUsersMetricTests.mjs b/services/web/test/acceptance/src/ActiveUsersMetricTests.mjs
index 8fbb661148..a7ca912ef2 100644
--- a/services/web/test/acceptance/src/ActiveUsersMetricTests.mjs
+++ b/services/web/test/acceptance/src/ActiveUsersMetricTests.mjs
@@ -1,6 +1,6 @@
import { promisify } from 'node:util'
import { expect } from 'chai'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
import MetricsHelper from './helpers/metrics.mjs'
import UserHelper from './helpers/User.mjs'
const sleep = promisify(setTimeout)
diff --git a/services/web/test/acceptance/src/AddSecondaryEmailTests.mjs b/services/web/test/acceptance/src/AddSecondaryEmailTests.mjs
index c153416fe6..d56d7e877e 100644
--- a/services/web/test/acceptance/src/AddSecondaryEmailTests.mjs
+++ b/services/web/test/acceptance/src/AddSecondaryEmailTests.mjs
@@ -3,7 +3,7 @@ import UserHelper from './helpers/User.mjs'
import logger from '@overleaf/logger'
import sinon from 'sinon'
import { db } from '../../../app/src/infrastructure/mongodb.js'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
const User = UserHelper.promises
diff --git a/services/web/test/acceptance/src/AuthorizationTests.mjs b/services/web/test/acceptance/src/AuthorizationTests.mjs
index 63001af6f8..dce54f438b 100644
--- a/services/web/test/acceptance/src/AuthorizationTests.mjs
+++ b/services/web/test/acceptance/src/AuthorizationTests.mjs
@@ -2,7 +2,7 @@ import { expect } from 'chai'
import UserHelper from './helpers/User.mjs'
import request from './helpers/request.js'
import settings from '@overleaf/settings'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
import expectErrorResponse from './helpers/expectErrorResponse.mjs'
import { promisify } from '@overleaf/promise-utils'
diff --git a/services/web/test/acceptance/src/DeletionTests.mjs b/services/web/test/acceptance/src/DeletionTests.mjs
index d7a1ff8896..39d1ba053d 100644
--- a/services/web/test/acceptance/src/DeletionTests.mjs
+++ b/services/web/test/acceptance/src/DeletionTests.mjs
@@ -7,7 +7,7 @@ import async from 'async'
import { expect } from 'chai'
import settings from '@overleaf/settings'
import { db, ObjectId } from '../../../app/src/infrastructure/mongodb.js'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
import MockDocstoreApiClass from './mocks/MockDocstoreApi.mjs'
import MockChatApiClass from './mocks/MockChatApi.mjs'
import MockGitBridgeApiClass from './mocks/MockGitBridgeApi.mjs'
diff --git a/services/web/test/acceptance/src/Init.mjs b/services/web/test/acceptance/src/Init.mjs
index c491ba3636..2179e27aeb 100644
--- a/services/web/test/acceptance/src/Init.mjs
+++ b/services/web/test/acceptance/src/Init.mjs
@@ -1,5 +1,5 @@
import './helpers/InitApp.mjs'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
import MockAnalyticsApi from './mocks/MockAnalyticsApi.mjs'
import MockChatApi from './mocks/MockChatApi.mjs'
diff --git a/services/web/test/acceptance/src/MongoHelper.mjs b/services/web/test/acceptance/src/MongoHelper.mjs
index dc7ddbeab6..1821477bdf 100644
--- a/services/web/test/acceptance/src/MongoHelper.mjs
+++ b/services/web/test/acceptance/src/MongoHelper.mjs
@@ -3,12 +3,11 @@ import mongodb from 'mongodb-legacy'
import mongoose from 'mongoose'
import { User as UserModel } from '../../../app/src/models/User.mjs'
import { db } from '../../../app/src/infrastructure/mongodb.js'
-import {
- normalizeQuery,
- normalizeMultiQuery,
-} from '../../../app/src/Features/Helpers/Mongo.js'
+import MongoHelpers from '../../../app/src/Features/Helpers/Mongo.mjs'
import UserHelper from './helpers/User.mjs'
+const { normalizeQuery, normalizeMultiQuery } = MongoHelpers
+
const User = UserHelper.promises
const NativeObjectId = mongodb.ObjectId
diff --git a/services/web/test/acceptance/src/PrimaryEmailCheckTests.mjs b/services/web/test/acceptance/src/PrimaryEmailCheckTests.mjs
index bc85f96a5d..16fcdf5ebb 100644
--- a/services/web/test/acceptance/src/PrimaryEmailCheckTests.mjs
+++ b/services/web/test/acceptance/src/PrimaryEmailCheckTests.mjs
@@ -1,7 +1,7 @@
import UserHelper from './helpers/UserHelper.mjs'
import Settings from '@overleaf/settings'
import { expect } from 'chai'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
import MockV1ApiClass from './mocks/MockV1Api.mjs'
import SubscriptionHelper from './helpers/Subscription.mjs'
diff --git a/services/web/test/acceptance/src/ProjectCRUDTests.mjs b/services/web/test/acceptance/src/ProjectCRUDTests.mjs
index 2c6d638e9f..12d4e3e0c7 100644
--- a/services/web/test/acceptance/src/ProjectCRUDTests.mjs
+++ b/services/web/test/acceptance/src/ProjectCRUDTests.mjs
@@ -4,7 +4,7 @@ import { Project } from '../../../app/src/models/Project.mjs'
import mongodb from 'mongodb-legacy'
import cheerio from 'cheerio'
import { Subscription } from '../../../app/src/models/Subscription.mjs'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
const ObjectId = mongodb.ObjectId
diff --git a/services/web/test/acceptance/src/ProjectInviteTests.mjs b/services/web/test/acceptance/src/ProjectInviteTests.mjs
index 0debf26169..525a1bbc82 100644
--- a/services/web/test/acceptance/src/ProjectInviteTests.mjs
+++ b/services/web/test/acceptance/src/ProjectInviteTests.mjs
@@ -4,7 +4,7 @@ import User from './helpers/User.mjs'
import settings from '@overleaf/settings'
import CollaboratorsEmailHandler from '../../../app/src/Features/Collaborators/CollaboratorsEmailHandler.mjs'
import CollaboratorsInviteHelper from '../../../app/src/Features/Collaborators/CollaboratorsInviteHelper.mjs'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
import sinon from 'sinon'
let generateTokenSpy
diff --git a/services/web/test/acceptance/src/RegistrationTests.mjs b/services/web/test/acceptance/src/RegistrationTests.mjs
index f062d3d314..c5fd66def7 100644
--- a/services/web/test/acceptance/src/RegistrationTests.mjs
+++ b/services/web/test/acceptance/src/RegistrationTests.mjs
@@ -3,7 +3,7 @@ import async from 'async'
import metrics from './helpers/metrics.mjs'
import User from './helpers/User.mjs'
import redis from './helpers/redis.mjs'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
const UserPromises = User.promises
diff --git a/services/web/test/acceptance/src/SettingsTests.mjs b/services/web/test/acceptance/src/SettingsTests.mjs
index 52378f1044..fc8bfa34f4 100644
--- a/services/web/test/acceptance/src/SettingsTests.mjs
+++ b/services/web/test/acceptance/src/SettingsTests.mjs
@@ -12,7 +12,7 @@ import { expect } from 'chai'
import async from 'async'
import User from './helpers/User.mjs'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
describe('SettingsPage', function () {
beforeEach(function (done) {
diff --git a/services/web/test/acceptance/src/UserHelperTests.mjs b/services/web/test/acceptance/src/UserHelperTests.mjs
index ab229133b6..b48af2ef81 100644
--- a/services/web/test/acceptance/src/UserHelperTests.mjs
+++ b/services/web/test/acceptance/src/UserHelperTests.mjs
@@ -1,6 +1,6 @@
import AuthenticationManager from '../../../app/src/Features/Authentication/AuthenticationManager.mjs'
import UserHelper from './helpers/UserHelper.mjs'
-import Features from '../../../app/src/infrastructure/Features.js'
+import Features from '../../../app/src/infrastructure/Features.mjs'
import { expect } from 'chai'
describe('UserHelper', function () {
diff --git a/services/web/test/smoke/src/steps/001_clearRateLimits.mjs b/services/web/test/smoke/src/steps/001_clearRateLimits.mjs
index 45e59f2b8c..953c3fca62 100644
--- a/services/web/test/smoke/src/steps/001_clearRateLimits.mjs
+++ b/services/web/test/smoke/src/steps/001_clearRateLimits.mjs
@@ -3,7 +3,7 @@ import {
overleafLoginRateLimiter,
openProjectRateLimiter,
} from '../../../../app/src/infrastructure/RateLimiter.js'
-import LoginRateLimiter from '../../../../app/src/Features/Security/LoginRateLimiter.js'
+import LoginRateLimiter from '../../../../app/src/Features/Security/LoginRateLimiter.mjs'
async function clearLoginRateLimit() {
await LoginRateLimiter.promises.recordSuccessfulLogin(Settings.smokeTest.user)
diff --git a/services/web/test/unit/bootstrap.js b/services/web/test/unit/bootstrap.js
index 00bcc3e958..82a1b04072 100644
--- a/services/web/test/unit/bootstrap.js
+++ b/services/web/test/unit/bootstrap.js
@@ -66,10 +66,7 @@ function getSandboxedModuleRequires() {
'@overleaf/logger': globalStubs.logger,
}
- const internalModules = [
- '../../app/src/Features/Errors/Errors',
- '../../app/src/Features/Helpers/Mongo',
- ]
+ const internalModules = ['../../app/src/Features/Errors/Errors']
const externalLibs = [
'async',
'bull',
diff --git a/services/web/test/unit/src/Analytics/AnalyticsController.test.mjs b/services/web/test/unit/src/Analytics/AnalyticsController.test.mjs
index c31900112b..be86f3bd04 100644
--- a/services/web/test/unit/src/Analytics/AnalyticsController.test.mjs
+++ b/services/web/test/unit/src/Analytics/AnalyticsController.test.mjs
@@ -1,6 +1,6 @@
import { vi } from 'vitest'
import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
const modulePath = new URL(
'../../../../app/src/Features/Analytics/AnalyticsController.mjs',
import.meta.url
@@ -33,7 +33,7 @@ describe('AnalyticsController', function () {
})
)
- vi.doMock('../../../../app/src/infrastructure/Features.js', () => ({
+ vi.doMock('../../../../app/src/infrastructure/Features.mjs', () => ({
default: ctx.Features,
}))
@@ -47,7 +47,7 @@ describe('AnalyticsController', function () {
ctx.controller = (await import(modulePath)).default
- ctx.res = new MockResponse()
+ ctx.res = new MockResponse(vi)
})
describe('updateEditingSession', function () {
diff --git a/services/web/test/unit/src/Analytics/AnalyticsManager.test.mjs b/services/web/test/unit/src/Analytics/AnalyticsManager.test.mjs
index 86b95b60c2..82c5d741c6 100644
--- a/services/web/test/unit/src/Analytics/AnalyticsManager.test.mjs
+++ b/services/web/test/unit/src/Analytics/AnalyticsManager.test.mjs
@@ -1,8 +1,8 @@
import { vi, assert } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequestVitest.mjs'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
import mongodb from 'mongodb-legacy'
const { ObjectId } = mongodb
@@ -395,9 +395,9 @@ describe('AnalyticsManager', function () {
}))
ctx.AnalyticsManager = (await import(MODULE_PATH)).default
- ctx.req = new MockRequest()
+ ctx.req = new MockRequest(vi)
ctx.req.session = {}
- ctx.res = new MockResponse()
+ ctx.res = new MockResponse(vi)
ctx.next = () => {}
})
diff --git a/services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddleware.test.mjs b/services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddleware.test.mjs
index eeed2f2850..dcad6a08cd 100644
--- a/services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddleware.test.mjs
+++ b/services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddleware.test.mjs
@@ -1,7 +1,7 @@
import { assert, vi } from 'vitest'
import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequestVitest.mjs'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
const MODULE_PATH = new URL(
'../../../../app/src/Features/Analytics/AnalyticsUTMTrackingMiddleware',
@@ -13,8 +13,8 @@ describe('AnalyticsUTMTrackingMiddleware', function () {
ctx.analyticsId = 'ecdb935a-52f3-4f91-aebc-7a70d2ffbb55'
ctx.userId = '61795fcb013504bb7b663092'
- ctx.req = new MockRequest()
- ctx.res = new MockResponse()
+ ctx.req = new MockRequest(vi)
+ ctx.res = new MockResponse(vi)
ctx.next = sinon.stub().returns()
ctx.req.session = {
user: {
diff --git a/services/web/test/unit/src/Analytics/EmailChangeHelpers.test.mjs b/services/web/test/unit/src/Analytics/EmailChangeHelpers.test.mjs
index 8fb9371e05..bf414139ae 100644
--- a/services/web/test/unit/src/Analytics/EmailChangeHelpers.test.mjs
+++ b/services/web/test/unit/src/Analytics/EmailChangeHelpers.test.mjs
@@ -29,7 +29,9 @@ describe('EmailChangeHelper', function () {
)
EmailChangeHelpers = (
- await import('../../../../app/src/Features/Analytics/EmailChangeHelper')
+ await import(
+ '../../../../app/src/Features/Analytics/EmailChangeHelper.mjs'
+ )
).default
})
diff --git a/services/web/test/unit/src/Authentication/AuthenticationController.test.mjs b/services/web/test/unit/src/Authentication/AuthenticationController.test.mjs
index 72f1e7ab33..17273d723f 100644
--- a/services/web/test/unit/src/Authentication/AuthenticationController.test.mjs
+++ b/services/web/test/unit/src/Authentication/AuthenticationController.test.mjs
@@ -1,10 +1,10 @@
import { beforeEach, describe, it, vi, expect } from 'vitest'
import sinon from 'sinon'
import tk from 'timekeeper'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequestVitest.mjs'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
import mongodb from 'mongodb-legacy'
-import AuthenticationErrors from '../../../../app/src/Features/Authentication/AuthenticationErrors.js'
+import AuthenticationErrors from '../../../../app/src/Features/Authentication/AuthenticationErrors.mjs'
const modulePath =
'../../../../app/src/Features/Authentication/AuthenticationController.mjs'
@@ -64,8 +64,8 @@ describe('AuthenticationController', function () {
},
}
ctx.password = 'banana'
- ctx.req = new MockRequest()
- ctx.res = new MockResponse()
+ ctx.req = new MockRequest(vi)
+ ctx.res = new MockResponse(vi)
ctx.callback = sinon.stub()
ctx.next = sinon.stub()
ctx.req.session.analyticsId = 'abc-123'
diff --git a/services/web/test/unit/src/Authentication/AuthenticationManager.test.mjs b/services/web/test/unit/src/Authentication/AuthenticationManager.test.mjs
index 2a85092530..c56112835b 100644
--- a/services/web/test/unit/src/Authentication/AuthenticationManager.test.mjs
+++ b/services/web/test/unit/src/Authentication/AuthenticationManager.test.mjs
@@ -1,7 +1,7 @@
import { vi, expect } from 'vitest'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
-import AuthenticationErrors from '../../../../app/src/Features/Authentication/AuthenticationErrors.js'
+import AuthenticationErrors from '../../../../app/src/Features/Authentication/AuthenticationErrors.mjs'
import tk from 'timekeeper'
import bcrypt from 'bcrypt'
diff --git a/services/web/test/unit/src/Authorization/AuthorizationManager.test.mjs b/services/web/test/unit/src/Authorization/AuthorizationManager.test.mjs
index 9b80678e30..8a5d7bde43 100644
--- a/services/web/test/unit/src/Authorization/AuthorizationManager.test.mjs
+++ b/services/web/test/unit/src/Authorization/AuthorizationManager.test.mjs
@@ -1,7 +1,7 @@
import { beforeEach, describe, it, vi, expect } from 'vitest'
import sinon from 'sinon'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
-import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.mjs'
import PublicAccessLevels from '../../../../app/src/Features/Authorization/PublicAccessLevels.mjs'
import mongodb from 'mongodb-legacy'
const modulePath =
diff --git a/services/web/test/unit/src/BetaProgram/BetaProgramController.test.mjs b/services/web/test/unit/src/BetaProgram/BetaProgramController.test.mjs
index b1fc8cf2fa..a80a24e3c9 100644
--- a/services/web/test/unit/src/BetaProgram/BetaProgramController.test.mjs
+++ b/services/web/test/unit/src/BetaProgram/BetaProgramController.test.mjs
@@ -1,7 +1,7 @@
import { expect, vi } from 'vitest'
import path from 'node:path'
import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
import { fileURLToPath } from 'node:url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
@@ -74,7 +74,7 @@ describe('BetaProgramController', function () {
)
ctx.BetaProgramController = (await import(modulePath)).default
- ctx.res = new MockResponse()
+ ctx.res = new MockResponse(vi)
ctx.next = sinon.stub()
})
@@ -122,7 +122,7 @@ describe('BetaProgramController', function () {
it("should not redirect to '/beta/participate'", function (ctx) {
ctx.BetaProgramController.optIn(ctx.req, ctx.res, ctx.next)
- ctx.res.redirect.callCount.should.equal(0)
+ expect(ctx.res.redirect).not.toHaveBeenCalled()
})
it('should produce an error', async function (ctx) {
@@ -248,7 +248,7 @@ describe('BetaProgramController', function () {
it('should not render the opt-in page', function (ctx) {
ctx.BetaProgramController.optInPage(ctx.req, ctx.res, ctx.next)
- ctx.res.render.callCount.should.equal(0)
+ expect(ctx.res.render).not.toHaveBeenCalled()
})
it('should produce an error', async function (ctx) {
diff --git a/services/web/test/unit/src/Chat/ChatApiHandler.test.mjs b/services/web/test/unit/src/Chat/ChatApiHandler.test.mjs
index a204c64dd7..83cc9d23b9 100644
--- a/services/web/test/unit/src/Chat/ChatApiHandler.test.mjs
+++ b/services/web/test/unit/src/Chat/ChatApiHandler.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
import { RequestFailedError } from '@overleaf/fetch-utils'
diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsController.test.mjs b/services/web/test/unit/src/Collaborators/CollaboratorsController.test.mjs
index 471092252a..922673f436 100644
--- a/services/web/test/unit/src/Collaborators/CollaboratorsController.test.mjs
+++ b/services/web/test/unit/src/Collaborators/CollaboratorsController.test.mjs
@@ -2,8 +2,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import sinon from 'sinon'
import 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'
+import MockRequest from '../helpers/MockRequestVitest.mjs'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
const ObjectId = mongodb.ObjectId
@@ -16,8 +16,8 @@ vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
describe('CollaboratorsController', function () {
beforeEach(async function (ctx) {
- ctx.res = new MockResponse()
- ctx.req = new MockRequest()
+ ctx.res = new MockResponse(vi)
+ ctx.req = new MockRequest(vi)
ctx.user = { _id: new ObjectId() }
ctx.projectId = new ObjectId()
diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsGetter.test.mjs b/services/web/test/unit/src/Collaborators/CollaboratorsGetter.test.mjs
index cd6506c7d4..9d304f421d 100644
--- a/services/web/test/unit/src/Collaborators/CollaboratorsGetter.test.mjs
+++ b/services/web/test/unit/src/Collaborators/CollaboratorsGetter.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import Path from 'path'
+import Path from 'node:path'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
import { Project } from '../../../../app/src/models/Project.mjs'
diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsHandler.test.mjs b/services/web/test/unit/src/Collaborators/CollaboratorsHandler.test.mjs
index fa4e4bc2b2..f9e571449b 100644
--- a/services/web/test/unit/src/Collaborators/CollaboratorsHandler.test.mjs
+++ b/services/web/test/unit/src/Collaborators/CollaboratorsHandler.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
import { Project } from '../../../../app/src/models/Project.mjs'
diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsInviteController.test.mjs b/services/web/test/unit/src/Collaborators/CollaboratorsInviteController.test.mjs
index 2aaf02dbb5..7cfa9c71c2 100644
--- a/services/web/test/unit/src/Collaborators/CollaboratorsInviteController.test.mjs
+++ b/services/web/test/unit/src/Collaborators/CollaboratorsInviteController.test.mjs
@@ -1,7 +1,7 @@
import { expect, vi } from 'vitest'
import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequestVitest.mjs'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
import mongodb from 'mongodb-legacy'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
import _ from 'lodash'
@@ -214,8 +214,8 @@ describe('CollaboratorsInviteController', function () {
ctx.CollaboratorsInviteController = (await import(MODULE_PATH)).default
- ctx.res = new MockResponse()
- ctx.req = new MockRequest()
+ ctx.res = new MockResponse(vi)
+ ctx.req = new MockRequest(vi)
ctx.next = sinon.stub()
})
@@ -248,8 +248,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should produce a list of invite objects', function (ctx) {
- ctx.res.json.callCount.should.equal(1)
- ctx.res.json.calledWith({ invites: ctx.fakeInvites }).should.equal(true)
+ expect(ctx.res.json).toHaveBeenCalledTimes(1)
+ expect(ctx.res.json).toHaveBeenCalledWith({ invites: ctx.fakeInvites })
})
it('should have called CollaboratorsInviteHandler.getAllInvites', function (ctx) {
@@ -312,8 +312,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should produce json response', function (ctx) {
- ctx.res.json.callCount.should.equal(1)
- expect(ctx.res.json.firstCall.args[0]).to.deep.equal({
+ expect(ctx.res.json).toHaveBeenCalledTimes(1)
+ expect(ctx.res.json.mock.calls[0][0]).to.deep.equal({
invite: ctx.inviteReducedData,
})
})
@@ -397,8 +397,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should produce json response without an invite', function (ctx) {
- ctx.res.json.callCount.should.equal(1)
- expect(ctx.res.json.firstCall.args[0]).to.deep.equal({
+ expect(ctx.res.json).toHaveBeenCalledTimes(1)
+ expect(ctx.res.json.mock.calls[0][0]).to.deep.equal({
invite: null,
})
})
@@ -442,8 +442,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should produce json response', function (ctx) {
- ctx.res.json.callCount.should.equal(1)
- expect(ctx.res.json.firstCall.args[0]).to.deep.equal({
+ expect(ctx.res.json).toHaveBeenCalledTimes(1)
+ expect(ctx.res.json.mock.calls[0][0]).to.deep.equal({
invite: ctx.inviteReducedData,
})
})
@@ -577,8 +577,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should produce json response with no invite, and an error property', function (ctx) {
- ctx.res.json.callCount.should.equal(1)
- expect(ctx.res.json.firstCall.args[0]).to.deep.equal({
+ expect(ctx.res.json).toHaveBeenCalledTimes(1)
+ expect(ctx.res.json.mock.calls[0][0]).to.deep.equal({
invite: null,
error: 'cannot_invite_non_user',
})
@@ -656,8 +656,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should reject action, return json response with error code', function (ctx) {
- ctx.res.json.callCount.should.equal(1)
- expect(ctx.res.json.firstCall.args[0]).to.deep.equal({
+ expect(ctx.res.json).toHaveBeenCalledTimes(1)
+ expect(ctx.res.json.mock.calls[0][0]).to.deep.equal({
invite: null,
error: 'cannot_invite_self',
})
@@ -702,7 +702,7 @@ describe('CollaboratorsInviteController', function () {
})
it('should send a 429 response', function (ctx) {
- ctx.res.sendStatus.calledWith(429).should.equal(true)
+ expect(ctx.res.sendStatus).toHaveBeenCalledWith(429)
})
it('should not call inviteToProject', function (ctx) {
@@ -760,8 +760,11 @@ describe('CollaboratorsInviteController', function () {
})
it('should render the view template', function (ctx) {
- ctx.res.render.callCount.should.equal(1)
- ctx.res.render.calledWith('project/invite/show').should.equal(true)
+ expect(ctx.res.render).toHaveBeenCalledTimes(1)
+ expect(ctx.res.render).toHaveBeenCalledWith(
+ 'project/invite/show',
+ expect.anything()
+ )
})
it('should not call next', function (ctx) {
@@ -826,32 +829,29 @@ describe('CollaboratorsInviteController', function () {
})
it('should redirect to the register page', function (ctx) {
- expect(ctx.res.render).to.not.have.been.called
- expect(ctx.res.redirect).to.have.been.calledOnce
- expect(ctx.res.redirect).to.have.been.calledWith('/register')
+ expect(ctx.res.render).not.toHaveBeenCalled()
+ expect(ctx.res.redirect).toHaveBeenCalledTimes(1)
+ expect(ctx.res.redirect).toHaveBeenCalledWith('/register')
})
})
describe('when user is already a member of the project', function () {
beforeEach(async function (ctx) {
- await new Promise(resolve => {
- ctx.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
- true
- )
- ctx.res.callback = () => resolve()
- ctx.CollaboratorsInviteController.viewInvite(
- ctx.req,
- ctx.res,
- ctx.next
- )
- })
+ ctx.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
+ true
+ )
+ await ctx.CollaboratorsInviteController.viewInvite(
+ ctx.req,
+ ctx.res,
+ ctx.next
+ )
})
it('should redirect to the project page', function (ctx) {
- ctx.res.redirect.callCount.should.equal(1)
- ctx.res.redirect
- .calledWith(`/project/${ctx.projectId}`)
- .should.equal(true)
+ expect(ctx.res.redirect).toHaveBeenCalledTimes(1)
+ expect(ctx.res.redirect).toHaveBeenCalledWith(
+ `/project/${ctx.projectId}`
+ )
})
it('should not call next with an error', function (ctx) {
@@ -987,8 +987,11 @@ describe('CollaboratorsInviteController', function () {
})
it('should render the not-valid view template', function (ctx) {
- ctx.res.render.callCount.should.equal(1)
- ctx.res.render.calledWith('project/invite/not-valid').should.equal(true)
+ expect(ctx.res.render).toHaveBeenCalledTimes(1)
+ expect(ctx.res.render).toHaveBeenCalledWith(
+ 'project/invite/not-valid',
+ expect.anything()
+ )
})
it('should not call next', function (ctx) {
@@ -1081,8 +1084,11 @@ describe('CollaboratorsInviteController', function () {
})
it('should render the not-valid view template', function (ctx) {
- ctx.res.render.callCount.should.equal(1)
- ctx.res.render.calledWith('project/invite/not-valid').should.equal(true)
+ expect(ctx.res.render).toHaveBeenCalledTimes(1)
+ expect(ctx.res.render).toHaveBeenCalledWith(
+ 'project/invite/not-valid',
+ expect.anything()
+ )
})
it('should not call next', function (ctx) {
@@ -1175,8 +1181,11 @@ describe('CollaboratorsInviteController', function () {
})
it('should render the not-valid view template', function (ctx) {
- ctx.res.render.callCount.should.equal(1)
- ctx.res.render.calledWith('project/invite/not-valid').should.equal(true)
+ expect(ctx.res.render).toHaveBeenCalledTimes(1)
+ expect(ctx.res.render).toHaveBeenCalledWith(
+ 'project/invite/not-valid',
+ expect.anything()
+ )
})
it('should not call next', function (ctx) {
@@ -1236,8 +1245,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should produce a 201 response', function (ctx) {
- ctx.res.sendStatus.callCount.should.equal(1)
- ctx.res.sendStatus.calledWith(201).should.equal(true)
+ expect(ctx.res.sendStatus).toHaveBeenCalledTimes(1)
+ expect(ctx.res.sendStatus).toHaveBeenCalledWith(201)
})
it('should have called generateNewInvite', function (ctx) {
@@ -1296,8 +1305,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should produce a 404 response when invite is null', function (ctx) {
- ctx.res.sendStatus.callCount.should.equal(1)
- ctx.res.sendStatus.should.have.been.calledWith(404)
+ expect(ctx.res.sendStatus).toHaveBeenCalledTimes(1)
+ expect(ctx.res.sendStatus).toHaveBeenCalledWith(404)
})
})
})
@@ -1318,7 +1327,7 @@ describe('CollaboratorsInviteController', function () {
})
it('should not produce a 201 response', function (ctx) {
- ctx.res.sendStatus.callCount.should.equal(0)
+ expect(ctx.res.sendStatus).not.toHaveBeenCalled()
})
it('should call next with the error', function (ctx) {
@@ -1355,8 +1364,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should produce a 204 response', function (ctx) {
- ctx.res.sendStatus.callCount.should.equal(1)
- ctx.res.sendStatus.should.have.been.calledWith(204)
+ expect(ctx.res.sendStatus).toHaveBeenCalledTimes(1)
+ expect(ctx.res.sendStatus).toHaveBeenCalledWith(204)
})
it('should have called revokeInvite', function (ctx) {
@@ -1402,7 +1411,7 @@ describe('CollaboratorsInviteController', function () {
})
it('should not produce a 201 response', function (ctx) {
- ctx.res.sendStatus.callCount.should.equal(0)
+ expect(ctx.res.sendStatus).not.toHaveBeenCalled()
})
it('should call next with the error', function (ctx) {
@@ -1439,8 +1448,8 @@ describe('CollaboratorsInviteController', function () {
})
it('should redirect to project page', function (ctx) {
- ctx.res.redirect.should.have.been.calledOnce
- ctx.res.redirect.should.have.been.calledWith(
+ expect(ctx.res.redirect).toHaveBeenCalledTimes(1)
+ expect(ctx.res.redirect).toHaveBeenCalledWith(
`/project/${ctx.projectId}`
)
})
@@ -1511,7 +1520,7 @@ describe('CollaboratorsInviteController', function () {
})
it('should not redirect to project page', function (ctx) {
- ctx.res.redirect.callCount.should.equal(0)
+ expect(ctx.res.redirect).not.toHaveBeenCalled()
})
it('should call next with the error', function (ctx) {
diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsInviteGetter.test.mjs b/services/web/test/unit/src/Collaborators/CollaboratorsInviteGetter.test.mjs
index 742516757a..16dd78c08f 100644
--- a/services/web/test/unit/src/Collaborators/CollaboratorsInviteGetter.test.mjs
+++ b/services/web/test/unit/src/Collaborators/CollaboratorsInviteGetter.test.mjs
@@ -1,7 +1,7 @@
import { vi, expect } from 'vitest'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
-import Crypto from 'crypto'
+import Crypto from 'node:crypto'
const { ObjectId } = mongodb
diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandler.test.mjs b/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandler.test.mjs
index 41e5e3eba5..47c7c4eefe 100644
--- a/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandler.test.mjs
+++ b/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandler.test.mjs
@@ -1,7 +1,7 @@
import { expect, vi } from 'vitest'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
-import Crypto from 'crypto'
+import Crypto from 'node:crypto'
const ObjectId = mongodb.ObjectId
diff --git a/services/web/test/unit/src/Collaborators/OwnershipTransferHandler.test.mjs b/services/web/test/unit/src/Collaborators/OwnershipTransferHandler.test.mjs
index 35e48e2e15..4cf14afcd1 100644
--- a/services/web/test/unit/src/Collaborators/OwnershipTransferHandler.test.mjs
+++ b/services/web/test/unit/src/Collaborators/OwnershipTransferHandler.test.mjs
@@ -1,6 +1,6 @@
import { vi, expect } from 'vitest'
import sinon from 'sinon'
-import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.mjs'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
import mongodb from 'mongodb-legacy'
diff --git a/services/web/test/unit/src/Compile/ClsiManager.test.mjs b/services/web/test/unit/src/Compile/ClsiManager.test.mjs
index e539747a5e..dc679c27ef 100644
--- a/services/web/test/unit/src/Compile/ClsiManager.test.mjs
+++ b/services/web/test/unit/src/Compile/ClsiManager.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import { setTimeout } from 'timers/promises'
+import { setTimeout } from 'node:timers/promises'
import sinon from 'sinon'
import tk from 'timekeeper'
import { RequestFailedError } from '@overleaf/fetch-utils'
diff --git a/services/web/test/unit/src/Compile/CompileController.test.mjs b/services/web/test/unit/src/Compile/CompileController.test.mjs
index ef2c22951f..d715d3e600 100644
--- a/services/web/test/unit/src/Compile/CompileController.test.mjs
+++ b/services/web/test/unit/src/Compile/CompileController.test.mjs
@@ -1,7 +1,7 @@
import { vi, expect } from 'vitest'
import sinon from 'sinon'
-import MockRequest from '../helpers/MockRequest.js'
-import MockResponse from '../helpers/MockResponse.js'
+import MockRequest from '../helpers/MockRequestVitest.mjs'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
import { Headers } from 'node-fetch'
import { ReadableString } from '@overleaf/stream-utils'
import { RequestFailedError } from '@overleaf/fetch-utils'
@@ -53,6 +53,8 @@ describe('CompileController', function () {
clsiCookie: {
key: 'cookie-key',
},
+ moduleImportSequence: [],
+ overleaf: {},
}
ctx.ClsiCookieManager = {
promises: {
@@ -168,9 +170,9 @@ describe('CompileController', function () {
ctx.projectId = 'abc123def456abc123def456'
ctx.build_id = '18fbe9e7564-30dcb2f71250c690'
ctx.next = sinon.stub()
- ctx.req = new MockRequest()
- ctx.res = new MockResponse()
- ctx.res = new MockResponse()
+ ctx.req = new MockRequest(vi)
+ ctx.res = new MockResponse(vi)
+ ctx.res = new MockResponse(vi)
})
describe('compile', function () {
@@ -288,7 +290,7 @@ describe('CompileController', function () {
})
it('should do the compile without the auto compile flag', function (ctx) {
- ctx.CompileManager.promises.compile.should.have.been.calledWith(
+ expect(ctx.CompileManager.promises.compile).to.have.been.calledWith(
ctx.projectId,
ctx.user_id,
{
@@ -409,7 +411,7 @@ describe('CompileController', function () {
it('should set the content-type of the response to application/json', async function (ctx) {
await ctx.CompileController.compileSubmission(ctx.req, ctx.res, ctx.next)
- ctx.res.contentType.calledWith('application/json').should.equal(true)
+ expect(ctx.res.contentType).toBeCalledWith('application/json')
})
it('should send a successful response reporting the status and files', async function (ctx) {
@@ -497,11 +499,11 @@ describe('CompileController', function () {
})
it('should set the content-type of the response to application/pdf', function (ctx) {
- ctx.res.contentType.calledWith('application/pdf').should.equal(true)
+ expect(ctx.res.contentType).toBeCalledWith('application/pdf')
})
it('should set the content-disposition header with a safe version of the project name', function (ctx) {
- ctx.res.setContentDisposition.should.be.calledWith('inline', {
+ expect(ctx.res.setContentDisposition).toBeCalledWith('inline', {
filename: 'test_namè__1.pdf',
})
})
@@ -556,7 +558,7 @@ describe('CompileController', function () {
it('should return 500', async function (ctx) {
await ctx.CompileController.downloadPdf(ctx.req, ctx.res, ctx.next)
// should it be 429 instead?
- ctx.res.sendStatus.calledWith(500).should.equal(true)
+ expect(ctx.res.sendStatus).toBeCalledWith(500)
ctx.CompileController._proxyToClsi.should.not.have.been.called
})
})
@@ -567,7 +569,7 @@ describe('CompileController', function () {
})
it('should return 500', async function (ctx) {
await ctx.CompileController.downloadPdf(ctx.req, ctx.res, ctx.next)
- ctx.res.sendStatus.calledWith(500).should.equal(true)
+ expect(ctx.res.sendStatus).toBeCalledWith(500)
ctx.CompileController._proxyToClsi.should.not.have.been.called
})
})
diff --git a/services/web/test/unit/src/Contact/ContactController.test.mjs b/services/web/test/unit/src/Contact/ContactController.test.mjs
index 06dd5b768a..3f85cf1c88 100644
--- a/services/web/test/unit/src/Contact/ContactController.test.mjs
+++ b/services/web/test/unit/src/Contact/ContactController.test.mjs
@@ -1,6 +1,6 @@
import { expect, vi } from 'vitest'
import sinon from 'sinon'
-import MockResponse from '../helpers/MockResponse.js'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
const modulePath = '../../../../app/src/Features/Contacts/ContactController.mjs'
describe('ContactController', function () {
@@ -33,7 +33,7 @@ describe('ContactController', function () {
ctx.ContactController = (await import(modulePath)).default
ctx.req = {}
- ctx.res = new MockResponse()
+ ctx.res = new MockResponse(vi)
})
describe('getContacts', function () {
@@ -130,7 +130,7 @@ describe('ContactController', function () {
it('should return a formatted list of contacts in contact list order, without holding accounts', async function (ctx) {
await new Promise((resolve, reject) => {
ctx.res.callback = () => {
- ctx.res.json.args[0][0].contacts.should.deep.equal([
+ ctx.res.json.mock.calls[0][0].contacts.should.deep.equal([
{
id: 'contact-1',
email: 'joe@example.com',
diff --git a/services/web/test/unit/src/DocumentUpdater/DocumentUpdaterHandler.test.mjs b/services/web/test/unit/src/DocumentUpdater/DocumentUpdaterHandler.test.mjs
index 6246b948ab..6afaa0b8bf 100644
--- a/services/web/test/unit/src/DocumentUpdater/DocumentUpdaterHandler.test.mjs
+++ b/services/web/test/unit/src/DocumentUpdater/DocumentUpdaterHandler.test.mjs
@@ -1,6 +1,6 @@
import { vi, expect } from 'vitest'
import sinon from 'sinon'
-import path from 'path'
+import path from 'node:path'
import mongodb from 'mongodb-legacy'
import nock from 'nock'
diff --git a/services/web/test/unit/src/Downloads/ProjectZipStreamManager.test.mjs b/services/web/test/unit/src/Downloads/ProjectZipStreamManager.test.mjs
index ab38897fb7..61f8a4ab77 100644
--- a/services/web/test/unit/src/Downloads/ProjectZipStreamManager.test.mjs
+++ b/services/web/test/unit/src/Downloads/ProjectZipStreamManager.test.mjs
@@ -10,7 +10,7 @@ import { vi } from 'vitest'
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import sinon from 'sinon'
-import { EventEmitter } from 'events'
+import { EventEmitter } from 'node:events'
const modulePath =
'../../../../app/src/Features/Downloads/ProjectZipStreamManager.mjs'
diff --git a/services/web/test/unit/src/Editor/EditorHttpController.test.mjs b/services/web/test/unit/src/Editor/EditorHttpController.test.mjs
index eb225a84a4..730d9c811a 100644
--- a/services/web/test/unit/src/Editor/EditorHttpController.test.mjs
+++ b/services/web/test/unit/src/Editor/EditorHttpController.test.mjs
@@ -2,8 +2,8 @@ import { beforeEach, describe, it, vi, expect } from 'vitest'
import sinon from 'sinon'
import 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'
+import MockRequest from '../helpers/MockRequestVitest.mjs'
+import MockResponse from '../helpers/MockResponseVitest.mjs'
const { ObjectId } = mongodb
@@ -52,8 +52,8 @@ describe('EditorHttpController', function () {
ctx.source = 'editor'
ctx.parentFolderId = 'mock-folder-id'
- ctx.req = new MockRequest()
- ctx.res = new MockResponse()
+ ctx.req = new MockRequest(vi)
+ ctx.res = new MockResponse(vi)
ctx.next = sinon.stub()
ctx.token = null
ctx.docLines = ['hello', 'overleaf']
@@ -302,7 +302,7 @@ describe('EditorHttpController', function () {
})
it('should return the project and privilege level', function (ctx) {
- expect(ctx.res.json).to.have.been.calledWith({
+ expect(ctx.res.json).toHaveBeenCalledWith({
project: ctx.projectView,
privilegeLevel: 'owner',
isRestrictedUser: false,
@@ -359,7 +359,7 @@ describe('EditorHttpController', function () {
})
it('should mark the user as restricted, and hide details of owner', function (ctx) {
- expect(ctx.res.json).to.have.been.calledWith({
+ expect(ctx.res.json).toHaveBeenCalledWith({
project: ctx.reducedProjectView,
privilegeLevel: 'readOnly',
isRestrictedUser: true,
@@ -415,7 +415,7 @@ describe('EditorHttpController', function () {
})
it('should mark the user as restricted', function (ctx) {
- expect(ctx.res.json).to.have.been.calledWith({
+ expect(ctx.res.json).toHaveBeenCalledWith({
project: ctx.reducedProjectView,
privilegeLevel: 'readOnly',
isRestrictedUser: true,
@@ -449,7 +449,7 @@ describe('EditorHttpController', function () {
})
it('should mark the user as being a token-access member', function (ctx) {
- expect(ctx.res.json).to.have.been.calledWith({
+ expect(ctx.res.json).toHaveBeenCalledWith({
project: ctx.projectView,
privilegeLevel: 'readAndWrite',
isRestrictedUser: false,
@@ -505,7 +505,7 @@ describe('EditorHttpController', function () {
})
it('should send the doc back as JSON', function (ctx) {
- expect(ctx.res.json).to.have.been.calledWith(ctx.doc)
+ expect(ctx.res.json).toHaveBeenCalledWith(ctx.doc)
})
})
@@ -528,7 +528,7 @@ describe('EditorHttpController', function () {
await new Promise(resolve => {
ctx.res.callback = () => {
expect(ctx.res.body).to.equal('"project_has_too_many_files_limit"')
- expect(ctx.res.status).to.have.been.calledWith(400)
+ expect(ctx.res.status).toHaveBeenCalledWith(400)
resolve()
}
ctx.EditorHttpController.addDoc(ctx.req, ctx.res)
@@ -565,7 +565,7 @@ describe('EditorHttpController', function () {
})
it('should send the folder back as JSON', function (ctx) {
- expect(ctx.res.json).to.have.been.calledWith(ctx.folder)
+ expect(ctx.res.json).toHaveBeenCalledWith(ctx.folder)
})
})
@@ -646,7 +646,7 @@ describe('EditorHttpController', function () {
})
it('should send back a success response', function (ctx) {
- expect(ctx.res.sendStatus).to.have.been.calledWith(204)
+ expect(ctx.res.sendStatus).toHaveBeenCalledWith(204)
})
})
describe('with long name', function () {
diff --git a/services/web/test/unit/src/Editor/EditorRealTimeController.test.mjs b/services/web/test/unit/src/Editor/EditorRealTimeController.test.mjs
index 79bcd269a7..f6862eb3ac 100644
--- a/services/web/test/unit/src/Editor/EditorRealTimeController.test.mjs
+++ b/services/web/test/unit/src/Editor/EditorRealTimeController.test.mjs
@@ -1,5 +1,5 @@
import { vi } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
const modulePath = path.join(
import.meta.dirname,
diff --git a/services/web/test/unit/src/Email/EmailBuilder.test.mjs b/services/web/test/unit/src/Email/EmailBuilder.test.mjs
index f47a4c0cee..d9970c4d4d 100644
--- a/services/web/test/unit/src/Email/EmailBuilder.test.mjs
+++ b/services/web/test/unit/src/Email/EmailBuilder.test.mjs
@@ -1,11 +1,11 @@
import { vi, expect } from 'vitest'
import cheerio from 'cheerio'
-import path from 'path'
+import path from 'node:path'
-import EmailMessageHelper from '../../../../app/src/Features/Email/EmailMessageHelper.js'
-import ctaEmailBody from '../../../../app/src/Features/Email/Bodies/cta-email.js'
-import NoCTAEmailBody from '../../../../app/src/Features/Email/Bodies/NoCTAEmailBody.js'
-import BaseWithHeaderEmailLayout from '../../../../app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.js'
+import EmailMessageHelper from '../../../../app/src/Features/Email/EmailMessageHelper.mjs'
+import ctaEmailBody from '../../../../app/src/Features/Email/Bodies/cta-email.mjs'
+import NoCTAEmailBody from '../../../../app/src/Features/Email/Bodies/NoCTAEmailBody.mjs'
+import BaseWithHeaderEmailLayout from '../../../../app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.mjs'
const MODULE_PATH = path.join(
import.meta.dirname,
diff --git a/services/web/test/unit/src/Email/EmailHandler.test.mjs b/services/web/test/unit/src/Email/EmailHandler.test.mjs
index c61f181e33..3d189efb3f 100644
--- a/services/web/test/unit/src/Email/EmailHandler.test.mjs
+++ b/services/web/test/unit/src/Email/EmailHandler.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
const MODULE_PATH = path.join(
diff --git a/services/web/test/unit/src/Email/EmailMessageHelper.test.mjs b/services/web/test/unit/src/Email/EmailMessageHelper.test.mjs
new file mode 100644
index 0000000000..2b1a11f03a
--- /dev/null
+++ b/services/web/test/unit/src/Email/EmailMessageHelper.test.mjs
@@ -0,0 +1,28 @@
+import path from 'node:path'
+import { expect } from 'vitest'
+
+const MODULE_PATH = path.join(
+ import.meta.dirname,
+ '../../../../app/src/Features/Email/EmailMessageHelper'
+)
+
+describe('EmailMessageHelper', function () {
+ beforeEach(async function (ctx) {
+ ctx.EmailMessageHelper = (await import(MODULE_PATH)).default
+ })
+ describe('cleanHTML', function () {
+ beforeEach(function (ctx) {
+ ctx.text = 'a message'
+ ctx.span = `${ctx.text}`
+ ctx.fullMessage = `${ctx.span}`
+ })
+ it('should remove HTML for plainText version', function (ctx) {
+ const processed = ctx.EmailMessageHelper.cleanHTML(ctx.fullMessage, true)
+ expect(processed).to.equal(ctx.text)
+ })
+ it('should keep HTML for HTML version but remove tags not allowed', function (ctx) {
+ const processed = ctx.EmailMessageHelper.cleanHTML(ctx.fullMessage, false)
+ expect(processed).to.equal(ctx.span)
+ })
+ })
+})
diff --git a/services/web/test/unit/src/Email/EmailMessageHelperTests.js b/services/web/test/unit/src/Email/EmailMessageHelperTests.js
deleted file mode 100644
index 563c51acf6..0000000000
--- a/services/web/test/unit/src/Email/EmailMessageHelperTests.js
+++ /dev/null
@@ -1,35 +0,0 @@
-const SandboxedModule = require('sandboxed-module')
-const path = require('path')
-const { expect } = require('chai')
-
-const MODULE_PATH = path.join(
- __dirname,
- '../../../../app/src/Features/Email/EmailMessageHelper'
-)
-
-describe('EmailMessageHelper', function () {
- beforeEach(function () {
- this.EmailMessageHelper = SandboxedModule.require(MODULE_PATH, {})
- })
- describe('cleanHTML', function () {
- beforeEach(function () {
- this.text = 'a message'
- this.span = `${this.text}`
- this.fullMessage = `${this.span}`
- })
- it('should remove HTML for plainText version', function () {
- const processed = this.EmailMessageHelper.cleanHTML(
- this.fullMessage,
- true
- )
- expect(processed).to.equal(this.text)
- })
- it('should keep HTML for HTML version but remove tags not allowed', function () {
- const processed = this.EmailMessageHelper.cleanHTML(
- this.fullMessage,
- false
- )
- expect(processed).to.equal(this.span)
- })
- })
-})
diff --git a/services/web/test/unit/src/Email/EmailSender.test.mjs b/services/web/test/unit/src/Email/EmailSender.test.mjs
index e8ab70163f..81cd742d9b 100644
--- a/services/web/test/unit/src/Email/EmailSender.test.mjs
+++ b/services/web/test/unit/src/Email/EmailSender.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
const MODULE_PATH = path.join(
diff --git a/services/web/test/unit/src/History/HistoryController.test.mjs b/services/web/test/unit/src/History/HistoryController.test.mjs
index ce826d1c0a..4a06a1cf59 100644
--- a/services/web/test/unit/src/History/HistoryController.test.mjs
+++ b/services/web/test/unit/src/History/HistoryController.test.mjs
@@ -118,7 +118,7 @@ describe('HistoryController', function () {
})
)
- vi.doMock('../../../../app/src/infrastructure/Features.js', () => ({
+ vi.doMock('../../../../app/src/infrastructure/Features.mjs', () => ({
default: (ctx.Features = sinon.stub().withArgs('saas').returns(true)),
}))
diff --git a/services/web/test/unit/src/Institutions/InstitutionsAPI.test.mjs b/services/web/test/unit/src/Institutions/InstitutionsAPI.test.mjs
index 34c56eba2a..f4b3921c92 100644
--- a/services/web/test/unit/src/Institutions/InstitutionsAPI.test.mjs
+++ b/services/web/test/unit/src/Institutions/InstitutionsAPI.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
diff --git a/services/web/test/unit/src/Institutions/InstitutionsManager.test.mjs b/services/web/test/unit/src/Institutions/InstitutionsManager.test.mjs
index 77a0dea3f1..ab877012f1 100644
--- a/services/web/test/unit/src/Institutions/InstitutionsManager.test.mjs
+++ b/services/web/test/unit/src/Institutions/InstitutionsManager.test.mjs
@@ -1,8 +1,8 @@
import { vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
-import Features from '../../../../app/src/infrastructure/Features.js'
+import Features from '../../../../app/src/infrastructure/Features.mjs'
const modulePath = path.join(
import.meta.dirname,
'../../../../app/src/Features/Institutions/InstitutionsManager'
diff --git a/services/web/test/unit/src/Project/ProjectController.test.mjs b/services/web/test/unit/src/Project/ProjectController.test.mjs
index 8d66b039e3..4382654ce9 100644
--- a/services/web/test/unit/src/Project/ProjectController.test.mjs
+++ b/services/web/test/unit/src/Project/ProjectController.test.mjs
@@ -1,6 +1,6 @@
import { beforeEach, describe, it, vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
const { ObjectId } = mongodb
diff --git a/services/web/test/unit/src/Project/ProjectListController.test.mjs b/services/web/test/unit/src/Project/ProjectListController.test.mjs
index 28307bd9e9..8c05193e90 100644
--- a/services/web/test/unit/src/Project/ProjectListController.test.mjs
+++ b/services/web/test/unit/src/Project/ProjectListController.test.mjs
@@ -2,6 +2,7 @@ import { beforeEach, describe, it, expect, vi } from 'vitest'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
+import Settings from '@overleaf/settings'
const ObjectId = mongodb.ObjectId
@@ -64,6 +65,7 @@ describe('ProjectListController', function () {
},
]
ctx.settings = {
+ ...Settings,
siteUrl: 'https://overleaf.com',
}
ctx.onboardingDataCollection = {
diff --git a/services/web/test/unit/src/Security/LoginRateLimiter.test.mjs b/services/web/test/unit/src/Security/LoginRateLimiter.test.mjs
new file mode 100644
index 0000000000..69d052ca90
--- /dev/null
+++ b/services/web/test/unit/src/Security/LoginRateLimiter.test.mjs
@@ -0,0 +1,77 @@
+import { vi, expect } from 'vitest'
+import sinon from 'sinon'
+const modulePath = '../../../../app/src/Features/Security/LoginRateLimiter'
+
+describe('LoginRateLimiter', function () {
+ beforeEach(async function (ctx) {
+ ctx.email = 'bob@bob.com'
+ ctx.rateLimiter = {
+ consume: sinon.stub().resolves(),
+ delete: sinon.stub().resolves(),
+ }
+ ctx.RateLimiter = {
+ RateLimiter: sinon.stub().withArgs('login').returns(ctx.rateLimiter),
+ }
+
+ vi.doMock(
+ '../../../../app/src/infrastructure/RateLimiter',
+ () => ctx.RateLimiter
+ )
+
+ ctx.LoginRateLimiter = (await import(modulePath)).default
+ })
+
+ describe('processLoginRequest', function () {
+ it('should consume points', async function (ctx) {
+ await ctx.LoginRateLimiter.promises.processLoginRequest(ctx.email)
+ expect(ctx.rateLimiter.consume).to.have.been.calledWith(ctx.email)
+ })
+
+ describe('when login is allowed', function () {
+ it('should call pass allow=true', async function (ctx) {
+ const allow = await ctx.LoginRateLimiter.promises.processLoginRequest(
+ ctx.email
+ )
+ expect(allow).to.equal(true)
+ })
+ })
+
+ describe('when login is blocked', function () {
+ beforeEach(function (ctx) {
+ ctx.rateLimiter.consume.rejects({ remainingPoints: 0 })
+ })
+
+ it('should call pass allow=false', async function (ctx) {
+ const allow = await ctx.LoginRateLimiter.promises.processLoginRequest(
+ ctx.email
+ )
+ expect(allow).to.equal(false)
+ })
+ })
+
+ describe('when consume produces an error', function () {
+ beforeEach(function (ctx) {
+ ctx.rateLimiter.consume.rejects(new Error('woops'))
+ })
+
+ it('should produce an error', async function (ctx) {
+ let error
+
+ try {
+ await ctx.LoginRateLimiter.promises.processLoginRequest(ctx.email)
+ } catch (err) {
+ error = err
+ }
+
+ expect(error).to.exist
+ })
+ })
+ })
+
+ describe('recordSuccessfulLogin', function () {
+ it('should clear the rate limit', async function (ctx) {
+ await ctx.LoginRateLimiter.promises.recordSuccessfulLogin(ctx.email)
+ expect(ctx.rateLimiter.delete).to.have.been.calledWith(ctx.email)
+ })
+ })
+})
diff --git a/services/web/test/unit/src/Security/LoginRateLimiterTests.js b/services/web/test/unit/src/Security/LoginRateLimiterTests.js
deleted file mode 100644
index 6dadaad8c2..0000000000
--- a/services/web/test/unit/src/Security/LoginRateLimiterTests.js
+++ /dev/null
@@ -1,80 +0,0 @@
-const SandboxedModule = require('sandboxed-module')
-const sinon = require('sinon')
-const { expect } = require('chai')
-const modulePath = require('path').join(
- __dirname,
- '../../../../app/src/Features/Security/LoginRateLimiter'
-)
-
-describe('LoginRateLimiter', function () {
- beforeEach(function () {
- this.email = 'bob@bob.com'
- this.rateLimiter = {
- consume: sinon.stub().resolves(),
- delete: sinon.stub().resolves(),
- }
- this.RateLimiter = {
- RateLimiter: sinon.stub().withArgs('login').returns(this.rateLimiter),
- }
-
- this.LoginRateLimiter = SandboxedModule.require(modulePath, {
- requires: {
- '../../infrastructure/RateLimiter': this.RateLimiter,
- },
- })
- })
-
- describe('processLoginRequest', function () {
- it('should consume points', async function () {
- await this.LoginRateLimiter.promises.processLoginRequest(this.email)
- expect(this.rateLimiter.consume).to.have.been.calledWith(this.email)
- })
-
- describe('when login is allowed', function () {
- it('should call pass allow=true', async function () {
- const allow = await this.LoginRateLimiter.promises.processLoginRequest(
- this.email
- )
- expect(allow).to.equal(true)
- })
- })
-
- describe('when login is blocked', function () {
- beforeEach(function () {
- this.rateLimiter.consume.rejects({ remainingPoints: 0 })
- })
-
- it('should call pass allow=false', async function () {
- const allow = await this.LoginRateLimiter.promises.processLoginRequest(
- this.email
- )
- expect(allow).to.equal(false)
- })
- })
-
- describe('when consume produces an error', function () {
- beforeEach(function () {
- this.rateLimiter.consume.rejects(new Error('woops'))
- })
-
- it('should produce an error', async function () {
- let error
-
- try {
- await this.LoginRateLimiter.promises.processLoginRequest(this.email)
- } catch (err) {
- error = err
- }
-
- expect(error).to.exist
- })
- })
- })
-
- describe('recordSuccessfulLogin', function () {
- it('should clear the rate limit', async function () {
- await this.LoginRateLimiter.promises.recordSuccessfulLogin(this.email)
- expect(this.rateLimiter.delete).to.have.been.calledWith(this.email)
- })
- })
-})
diff --git a/services/web/test/unit/src/SplitTests/SplitTestHandler.test.mjs b/services/web/test/unit/src/SplitTests/SplitTestHandler.test.mjs
index f3103679a2..a4c9dc61df 100644
--- a/services/web/test/unit/src/SplitTests/SplitTestHandler.test.mjs
+++ b/services/web/test/unit/src/SplitTests/SplitTestHandler.test.mjs
@@ -1,5 +1,5 @@
import { vi, assert, expect } from 'vitest'
-import Path from 'path'
+import Path from 'node:path'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
import MockRequest from '../helpers/MockRequest.js'
diff --git a/services/web/test/unit/src/SplitTests/SplitTestSessionHandler.test.mjs b/services/web/test/unit/src/SplitTests/SplitTestSessionHandler.test.mjs
index 33793ec1f6..a29bf22b22 100644
--- a/services/web/test/unit/src/SplitTests/SplitTestSessionHandler.test.mjs
+++ b/services/web/test/unit/src/SplitTests/SplitTestSessionHandler.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import Path from 'path'
+import Path from 'node:path'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
diff --git a/services/web/test/unit/src/Subscription/FeaturesUpdater.test.mjs b/services/web/test/unit/src/Subscription/FeaturesUpdater.test.mjs
index d34419f1bf..515b44ef70 100644
--- a/services/web/test/unit/src/Subscription/FeaturesUpdater.test.mjs
+++ b/services/web/test/unit/src/Subscription/FeaturesUpdater.test.mjs
@@ -1,7 +1,7 @@
import { vi, expect } from 'vitest'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
-import { AI_ADD_ON_CODE } from '../../../../app/src/Features/Subscription/AiHelper.js'
+import { AI_ADD_ON_CODE } from '../../../../app/src/Features/Subscription/AiHelper.mjs'
const { ObjectId } = mongodb
diff --git a/services/web/test/unit/src/Subscription/PaymentProviderEntities.test.mjs b/services/web/test/unit/src/Subscription/PaymentProviderEntities.test.mjs
index 8b60574599..404426e764 100644
--- a/services/web/test/unit/src/Subscription/PaymentProviderEntities.test.mjs
+++ b/services/web/test/unit/src/Subscription/PaymentProviderEntities.test.mjs
@@ -1,9 +1,9 @@
import { vi, expect } from 'vitest'
-import Errors from '../../../../app/src/Features/Subscription/Errors.js'
+import Errors from '../../../../app/src/Features/Subscription/Errors.mjs'
import PaymentProviderEntities from '../../../../app/src/Features/Subscription/PaymentProviderEntities.mjs'
-import { AI_ADD_ON_CODE } from '../../../../app/src/Features/Subscription/AiHelper.js'
+import { AI_ADD_ON_CODE } from '../../../../app/src/Features/Subscription/AiHelper.mjs'
import SubscriptionHelper from '../../../../app/src/Features/Subscription/SubscriptionHelper.mjs'
const {
diff --git a/services/web/test/unit/src/Subscription/RecurlyWrapper.test.mjs b/services/web/test/unit/src/Subscription/RecurlyWrapper.test.mjs
index 32f86ad9ad..6c91a361de 100644
--- a/services/web/test/unit/src/Subscription/RecurlyWrapper.test.mjs
+++ b/services/web/test/unit/src/Subscription/RecurlyWrapper.test.mjs
@@ -2,7 +2,7 @@ import { vi, assert, expect } from 'vitest'
import sinon from 'sinon'
import tk from 'timekeeper'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
-import SubscriptionErrors from '../../../../app/src/Features/Subscription/Errors.js'
+import SubscriptionErrors from '../../../../app/src/Features/Subscription/Errors.mjs'
import { RequestFailedError } from '@overleaf/fetch-utils'
const modulePath = '../../../../app/src/Features/Subscription/RecurlyWrapper'
diff --git a/services/web/test/unit/src/Subscription/SubscriptionController.test.mjs b/services/web/test/unit/src/Subscription/SubscriptionController.test.mjs
index 1d4e6ec607..98bc3d84fe 100644
--- a/services/web/test/unit/src/Subscription/SubscriptionController.test.mjs
+++ b/services/web/test/unit/src/Subscription/SubscriptionController.test.mjs
@@ -2,9 +2,9 @@ import { vi, assert, expect } from 'vitest'
import sinon from 'sinon'
import MockRequest from '../helpers/MockRequest.js'
import MockResponse from '../helpers/MockResponse.js'
-import SubscriptionErrors from '../../../../app/src/Features/Subscription/Errors.js'
+import SubscriptionErrors from '../../../../app/src/Features/Subscription/Errors.mjs'
import SubscriptionHelper from '../../../../app/src/Features/Subscription/SubscriptionHelper.mjs'
-import { AI_ADD_ON_CODE } from '../../../../app/src/Features/Subscription/AiHelper.js'
+import { AI_ADD_ON_CODE } from '../../../../app/src/Features/Subscription/AiHelper.mjs'
const modulePath =
'../../../../app/src/Features/Subscription/SubscriptionController.mjs'
diff --git a/services/web/test/unit/src/Subscription/V1SusbcriptionManager.test.mjs b/services/web/test/unit/src/Subscription/V1SusbcriptionManager.test.mjs
index 7c2e1f821c..c0aeace3b5 100644
--- a/services/web/test/unit/src/Subscription/V1SusbcriptionManager.test.mjs
+++ b/services/web/test/unit/src/Subscription/V1SusbcriptionManager.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
const modulePath = path.join(
import.meta.dirname,
diff --git a/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateSender.test.mjs b/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateSender.test.mjs
index 5537a4dc24..3ad0e6a176 100644
--- a/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateSender.test.mjs
+++ b/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateSender.test.mjs
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import mongodb from 'mongodb-legacy'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
const { ObjectId } = mongodb
diff --git a/services/web/test/unit/src/ThirdPartyDataStore/UpdateMerger.test.mjs b/services/web/test/unit/src/ThirdPartyDataStore/UpdateMerger.test.mjs
index d4290dce5d..58bc5f974d 100644
--- a/services/web/test/unit/src/ThirdPartyDataStore/UpdateMerger.test.mjs
+++ b/services/web/test/unit/src/ThirdPartyDataStore/UpdateMerger.test.mjs
@@ -1,6 +1,6 @@
import { vi, expect } from 'vitest'
import sinon from 'sinon'
-import { Writable } from 'stream'
+import { Writable } from 'node:stream'
import mongodb from 'mongodb-legacy'
const { ObjectId } = mongodb
diff --git a/services/web/test/unit/src/TokenAccess/TokenAccessController.test.mjs b/services/web/test/unit/src/TokenAccess/TokenAccessController.test.mjs
index 117a6b0b14..9fd93ac294 100644
--- a/services/web/test/unit/src/TokenAccess/TokenAccessController.test.mjs
+++ b/services/web/test/unit/src/TokenAccess/TokenAccessController.test.mjs
@@ -3,7 +3,7 @@ import sinon from 'sinon'
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 PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.mjs'
import UrlHelper from '../../../../app/src/Features/Helpers/UrlHelper.mjs'
const { getSafeRedirectPath } = UrlHelper
diff --git a/services/web/test/unit/src/TokenAccess/TokenAccessHandler.test.mjs b/services/web/test/unit/src/TokenAccess/TokenAccessHandler.test.mjs
index ffb8127725..0519bcb4bf 100644
--- a/services/web/test/unit/src/TokenAccess/TokenAccessHandler.test.mjs
+++ b/services/web/test/unit/src/TokenAccess/TokenAccessHandler.test.mjs
@@ -1,8 +1,8 @@
import { vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
-import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.js'
+import PrivilegeLevels from '../../../../app/src/Features/Authorization/PrivilegeLevels.mjs'
const modulePath = path.join(
import.meta.dirname,
diff --git a/services/web/test/unit/src/Uploads/ArchiveManager.test.mjs b/services/web/test/unit/src/Uploads/ArchiveManager.test.mjs
index b3aa7abc5d..4ab5e14b4f 100644
--- a/services/web/test/unit/src/Uploads/ArchiveManager.test.mjs
+++ b/services/web/test/unit/src/Uploads/ArchiveManager.test.mjs
@@ -8,8 +8,8 @@ import { vi, expect } from 'vitest'
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import sinon from 'sinon'
-import ArchiveErrors from '../../../../app/src/Features/Uploads/ArchiveErrors.js'
-import events from 'events'
+import ArchiveErrors from '../../../../app/src/Features/Uploads/ArchiveErrors.mjs'
+import events from 'node:events'
vi.mock('../../../../app/src/Features/Uploads/ArchiveErrors.js', () =>
vi.importActual('../../../../app/src/Features/Uploads/ArchiveErrors.js')
diff --git a/services/web/test/unit/src/Uploads/ProjectUploadController.test.mjs b/services/web/test/unit/src/Uploads/ProjectUploadController.test.mjs
index b36d596da2..a5b5ed410e 100644
--- a/services/web/test/unit/src/Uploads/ProjectUploadController.test.mjs
+++ b/services/web/test/unit/src/Uploads/ProjectUploadController.test.mjs
@@ -9,7 +9,7 @@ import { expect, vi } from 'vitest'
import sinon from 'sinon'
import MockRequest from '../helpers/MockRequest.js'
import MockResponse from '../helpers/MockResponse.js'
-import ArchiveErrors from '../../../../app/src/Features/Uploads/ArchiveErrors.js'
+import ArchiveErrors from '../../../../app/src/Features/Uploads/ArchiveErrors.mjs'
const modulePath =
'../../../../app/src/Features/Uploads/ProjectUploadController.mjs'
diff --git a/services/web/test/unit/src/User/UserGetter.test.mjs b/services/web/test/unit/src/User/UserGetter.test.mjs
index 71c711f320..8299dac405 100644
--- a/services/web/test/unit/src/User/UserGetter.test.mjs
+++ b/services/web/test/unit/src/User/UserGetter.test.mjs
@@ -1,18 +1,16 @@
import { vi, expect } from 'vitest'
import mongodb from 'mongodb-legacy'
-import assert from 'assert'
+import assert from 'node:assert'
import moment from 'moment'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
-import {
- normalizeQuery,
- normalizeMultiQuery,
-} from '../../../../app/src/Features/Helpers/Mongo.js'
+import MongoHelpers from '../../../../app/src/Features/Helpers/Mongo.mjs'
const modulePath = path.join(
import.meta.dirname,
'../../../../app/src/Features/User/UserGetter'
)
+const { normalizeQuery, normalizeMultiQuery } = MongoHelpers
vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
vi.importActual('../../../../app/src/Features/Errors/Errors.js')
@@ -60,8 +58,7 @@ describe('UserGetter', function () {
}
vi.doMock('../../../../app/src/Features/Helpers/Mongo', () => ({
- normalizeQuery,
- normalizeMultiQuery,
+ default: { normalizeQuery, normalizeMultiQuery },
}))
vi.doMock('../../../../app/src/infrastructure/mongodb', () => ctx.Mongo)
diff --git a/services/web/test/unit/src/User/UserPagesController.test.mjs b/services/web/test/unit/src/User/UserPagesController.test.mjs
index 0a05cbfb3c..5a5d301e55 100644
--- a/services/web/test/unit/src/User/UserPagesController.test.mjs
+++ b/services/web/test/unit/src/User/UserPagesController.test.mjs
@@ -1,5 +1,5 @@
import { expect, vi } from 'vitest'
-import assert from 'assert'
+import assert from 'node:assert'
import sinon from 'sinon'
import MockResponse from '../helpers/MockResponse.js'
import MockRequest from '../helpers/MockRequest.js'
diff --git a/services/web/test/unit/src/User/UserRegistrationHandler.test.mjs b/services/web/test/unit/src/User/UserRegistrationHandler.test.mjs
index ce1f478fbf..733e12a121 100644
--- a/services/web/test/unit/src/User/UserRegistrationHandler.test.mjs
+++ b/services/web/test/unit/src/User/UserRegistrationHandler.test.mjs
@@ -1,5 +1,5 @@
import { vi, expect } from 'vitest'
-import assert from 'assert'
+import assert from 'node:assert'
import sinon from 'sinon'
import EmailHelper from '../../../../app/src/Features/Helpers/EmailHelper.mjs'
diff --git a/services/web/test/unit/src/User/UserUpdater.test.mjs b/services/web/test/unit/src/User/UserUpdater.test.mjs
index fd0b68779e..be53b84435 100644
--- a/services/web/test/unit/src/User/UserUpdater.test.mjs
+++ b/services/web/test/unit/src/User/UserUpdater.test.mjs
@@ -1,12 +1,13 @@
import { vi, expect } from 'vitest'
-import { setTimeout } from 'timers/promises'
-import path from 'path'
+import { setTimeout } from 'node:timers/promises'
+import path from 'node:path'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
import tk from 'timekeeper'
-import { normalizeQuery } from '../../../../app/src/Features/Helpers/Mongo.js'
+import MongoHelpers from '../../../../app/src/Features/Helpers/Mongo.mjs'
import Errors from '../../../../app/src/Features/Errors/Errors.js'
+const { normalizeQuery } = MongoHelpers
const { ObjectId } = mongodb
const MODULE_PATH = path.join(
@@ -126,7 +127,7 @@ describe('UserUpdater', function () {
}
vi.doMock('../../../../app/src/Features/Helpers/Mongo', () => ({
- normalizeQuery,
+ default: { normalizeQuery },
}))
vi.doMock('../../../../app/src/infrastructure/mongodb', () => ctx.mongodb)
diff --git a/services/web/test/unit/src/UserMembership/UserMembershipViewModel.test.mjs b/services/web/test/unit/src/UserMembership/UserMembershipViewModel.test.mjs
index 590008d454..04b160f522 100644
--- a/services/web/test/unit/src/UserMembership/UserMembershipViewModel.test.mjs
+++ b/services/web/test/unit/src/UserMembership/UserMembershipViewModel.test.mjs
@@ -1,14 +1,12 @@
import { vi, expect } from 'vitest'
import sinon from 'sinon'
import mongodb from 'mongodb-legacy'
-import {
- isObjectIdInstance,
- normalizeQuery,
-} from '../../../../app/src/Features/Helpers/Mongo.js'
+import MongoHelpers from '../../../../app/src/Features/Helpers/Mongo.mjs'
const assertCalledWith = sinon.assert.calledWith
const assertNotCalled = sinon.assert.notCalled
+const { isObjectIdInstance, normalizeQuery } = MongoHelpers
const { ObjectId } = mongodb
const modulePath =
@@ -23,8 +21,7 @@ describe('UserMembershipViewModel', function () {
}))
vi.doMock('../../../../app/src/Features/Helpers/Mongo', () => ({
- isObjectIdInstance,
- normalizeQuery,
+ default: { isObjectIdInstance, normalizeQuery },
}))
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
diff --git a/services/web/test/unit/src/helpers/MockResponseVitest.mjs b/services/web/test/unit/src/helpers/MockResponseVitest.mjs
index cc82ced870..a2401ef465 100644
--- a/services/web/test/unit/src/helpers/MockResponseVitest.mjs
+++ b/services/web/test/unit/src/helpers/MockResponseVitest.mjs
@@ -1,4 +1,4 @@
-import Path from 'path'
+import Path from 'node:path'
import contentDisposition from 'content-disposition'
class MockResponse {
diff --git a/services/web/test/unit/src/infrastructure/Features.test.mjs b/services/web/test/unit/src/infrastructure/Features.test.mjs
new file mode 100644
index 0000000000..0e01b23433
--- /dev/null
+++ b/services/web/test/unit/src/infrastructure/Features.test.mjs
@@ -0,0 +1,151 @@
+import { vi, expect } from 'vitest'
+const modulePath = '../../../../app/src/infrastructure/Features.mjs'
+
+describe('Features', function () {
+ beforeEach(async function (ctx) {
+ vi.doMock('@overleaf/settings', () => ({
+ default: (ctx.settings = {
+ moduleImportSequence: [],
+ enabledLinkedFileTypes: [],
+ }),
+ }))
+
+ ctx.Features = (await import(modulePath)).default
+ })
+ describe('externalAuthenticationSystemUsed', function () {
+ describe('without any settings', function () {
+ it('should return false', function (ctx) {
+ expect(ctx.Features.externalAuthenticationSystemUsed()).to.be.false
+ })
+ })
+ describe('with ldap setting', function () {
+ beforeEach(function (ctx) {
+ ctx.settings.ldap = { enable: true }
+ })
+ it('should return true', function (ctx) {
+ expect(ctx.Features.externalAuthenticationSystemUsed()).to.be.true
+ })
+ })
+ describe('with saml setting', function () {
+ beforeEach(function (ctx) {
+ ctx.settings.saml = { enable: true }
+ })
+ it('should return true', function (ctx) {
+ expect(ctx.Features.externalAuthenticationSystemUsed()).to.be.true
+ })
+ })
+ describe('with oauth setting', function () {
+ beforeEach(function (ctx) {
+ ctx.settings.overleaf = { oauth: true }
+ })
+ it('should return true', function (ctx) {
+ expect(ctx.Features.externalAuthenticationSystemUsed()).to.be.true
+ })
+ })
+ })
+
+ describe('hasFeature', function () {
+ describe('without any settings', function () {
+ it('should return true', function (ctx) {
+ expect(ctx.Features.hasFeature('registration-page')).to.be.true
+ })
+ it('should return false', function (ctx) {
+ expect(ctx.Features.hasFeature('registration')).to.be.false
+ expect(ctx.Features.hasFeature('affiliations')).to.be.false
+ expect(ctx.Features.hasFeature('analytics')).to.be.false
+ expect(ctx.Features.hasFeature('git-bridge')).to.be.false
+ expect(ctx.Features.hasFeature('github-sync')).to.be.false
+ expect(ctx.Features.hasFeature('homepage')).to.be.false
+ expect(ctx.Features.hasFeature('link-url')).to.be.false
+ expect(ctx.Features.hasFeature('oauth')).to.be.false
+ expect(ctx.Features.hasFeature('saas')).to.be.false
+ expect(ctx.Features.hasFeature('references')).to.be.false
+ expect(ctx.Features.hasFeature('saml')).to.be.false
+ expect(ctx.Features.hasFeature('templates-server-pro')).to.be.false
+ })
+ })
+ describe('with settings', function () {
+ describe('empty overleaf object', function () {
+ beforeEach(function (ctx) {
+ ctx.settings.overleaf = {}
+ ctx.settings.apis = {}
+ })
+ it('should return true', function (ctx) {
+ expect(ctx.Features.hasFeature('saas')).to.be.true
+ expect(ctx.Features.hasFeature('registration')).to.be.true
+ })
+ it('should return false', function (ctx) {
+ expect(ctx.Features.hasFeature('affiliations')).to.be.false
+ expect(ctx.Features.hasFeature('analytics')).to.be.false
+ expect(ctx.Features.hasFeature('git-bridge')).to.be.false
+ expect(ctx.Features.hasFeature('github-sync')).to.be.false
+ expect(ctx.Features.hasFeature('homepage')).to.be.false
+ expect(ctx.Features.hasFeature('link-url')).to.be.false
+ expect(ctx.Features.hasFeature('oauth')).to.be.false
+ expect(ctx.Features.hasFeature('references')).to.be.false
+ expect(ctx.Features.hasFeature('saml')).to.be.false
+ expect(ctx.Features.hasFeature('templates-server-pro')).to.be.false
+ })
+ describe('with APIs', function () {
+ beforeEach(function (ctx) {
+ ctx.settings.apis = {
+ linkedUrlProxy: {
+ url: 'https://www.overleaf.com',
+ },
+ references: {
+ url: 'https://www.overleaf.com',
+ },
+ v1: {
+ url: 'https://www.overleaf.com',
+ },
+ }
+ })
+ it('should return true', function (ctx) {
+ expect(ctx.Features.hasFeature('affiliations')).to.be.true
+ expect(ctx.Features.hasFeature('analytics')).to.be.true
+ expect(ctx.Features.hasFeature('saas')).to.be.true
+ expect(ctx.Features.hasFeature('references')).to.be.true
+ expect(ctx.Features.hasFeature('registration')).to.be.true
+ })
+ it('should return false', function (ctx) {
+ expect(ctx.Features.hasFeature('link-url')).to.be.false
+ expect(ctx.Features.hasFeature('git-bridge')).to.be.false
+ expect(ctx.Features.hasFeature('github-sync')).to.be.false
+ expect(ctx.Features.hasFeature('homepage')).to.be.false
+ expect(ctx.Features.hasFeature('oauth')).to.be.false
+ expect(ctx.Features.hasFeature('saml')).to.be.false
+ expect(ctx.Features.hasFeature('templates-server-pro')).to.be.false
+ })
+ describe('with all other settings flags', function () {
+ beforeEach(function (ctx) {
+ ctx.settings.enableHomepage = true
+ ctx.settings.enableGitBridge = true
+ ctx.settings.enableGithubSync = true
+ ctx.settings.enableSaml = true
+ ctx.settings.oauth = true
+ ctx.settings.enabledLinkedFileTypes = ['url', 'project_file']
+ })
+ it('should return true or return value', function (ctx) {
+ expect(ctx.Features.hasFeature('link-url')).to.be.true
+ expect(ctx.Features.hasFeature('affiliations')).to.be.true
+ expect(ctx.Features.hasFeature('analytics')).to.be.true
+ expect(ctx.Features.hasFeature('github-sync')).to.be.true
+ expect(ctx.Features.hasFeature('git-bridge')).to.be.true
+ expect(ctx.Features.hasFeature('homepage')).to.be.true
+ expect(ctx.Features.hasFeature('link-url')).to.be.true
+ expect(ctx.Features.hasFeature('oauth')).to.be.true
+ expect(ctx.Features.hasFeature('saas')).to.be.true
+ expect(ctx.Features.hasFeature('references')).to.be.true
+ expect(ctx.Features.hasFeature('registration')).to.be.true
+ expect(ctx.Features.hasFeature('saml')).to.be.true
+ })
+ it('should return false', function (ctx) {
+ expect(ctx.Features.hasFeature('templates-server-pro')).to.be
+ .false
+ })
+ })
+ })
+ })
+ })
+ })
+})
diff --git a/services/web/test/unit/src/infrastructure/FeaturesTests.js b/services/web/test/unit/src/infrastructure/FeaturesTests.js
deleted file mode 100644
index dcdf1e4e62..0000000000
--- a/services/web/test/unit/src/infrastructure/FeaturesTests.js
+++ /dev/null
@@ -1,152 +0,0 @@
-const { expect } = require('chai')
-const modulePath = '../../../../app/src/infrastructure/Features.js'
-const SandboxedModule = require('sandboxed-module')
-
-describe('Features', function () {
- beforeEach(function () {
- this.Features = SandboxedModule.require(modulePath, {
- requires: {
- '@overleaf/settings': (this.settings = {
- moduleImportSequence: [],
- enabledLinkedFileTypes: [],
- }),
- },
- })
- })
- describe('externalAuthenticationSystemUsed', function () {
- describe('without any settings', function () {
- it('should return false', function () {
- expect(this.Features.externalAuthenticationSystemUsed()).to.be.false
- })
- })
- describe('with ldap setting', function () {
- beforeEach(function () {
- this.settings.ldap = { enable: true }
- })
- it('should return true', function () {
- expect(this.Features.externalAuthenticationSystemUsed()).to.be.true
- })
- })
- describe('with saml setting', function () {
- beforeEach(function () {
- this.settings.saml = { enable: true }
- })
- it('should return true', function () {
- expect(this.Features.externalAuthenticationSystemUsed()).to.be.true
- })
- })
- describe('with oauth setting', function () {
- beforeEach(function () {
- this.settings.overleaf = { oauth: true }
- })
- it('should return true', function () {
- expect(this.Features.externalAuthenticationSystemUsed()).to.be.true
- })
- })
- })
-
- describe('hasFeature', function () {
- describe('without any settings', function () {
- it('should return true', function () {
- expect(this.Features.hasFeature('registration-page')).to.be.true
- })
- it('should return false', function () {
- expect(this.Features.hasFeature('registration')).to.be.false
- expect(this.Features.hasFeature('affiliations')).to.be.false
- expect(this.Features.hasFeature('analytics')).to.be.false
- expect(this.Features.hasFeature('git-bridge')).to.be.false
- expect(this.Features.hasFeature('github-sync')).to.be.false
- expect(this.Features.hasFeature('homepage')).to.be.false
- expect(this.Features.hasFeature('link-url')).to.be.false
- expect(this.Features.hasFeature('oauth')).to.be.false
- expect(this.Features.hasFeature('saas')).to.be.false
- expect(this.Features.hasFeature('references')).to.be.false
- expect(this.Features.hasFeature('saml')).to.be.false
- expect(this.Features.hasFeature('templates-server-pro')).to.be.false
- })
- })
- describe('with settings', function () {
- describe('empty overleaf object', function () {
- beforeEach(function () {
- this.settings.overleaf = {}
- this.settings.apis = {}
- })
- it('should return true', function () {
- expect(this.Features.hasFeature('saas')).to.be.true
- expect(this.Features.hasFeature('registration')).to.be.true
- })
- it('should return false', function () {
- expect(this.Features.hasFeature('affiliations')).to.be.false
- expect(this.Features.hasFeature('analytics')).to.be.false
- expect(this.Features.hasFeature('git-bridge')).to.be.false
- expect(this.Features.hasFeature('github-sync')).to.be.false
- expect(this.Features.hasFeature('homepage')).to.be.false
- expect(this.Features.hasFeature('link-url')).to.be.false
- expect(this.Features.hasFeature('oauth')).to.be.false
- expect(this.Features.hasFeature('references')).to.be.false
- expect(this.Features.hasFeature('saml')).to.be.false
- expect(this.Features.hasFeature('templates-server-pro')).to.be.false
- })
- describe('with APIs', function () {
- beforeEach(function () {
- this.settings.apis = {
- linkedUrlProxy: {
- url: 'https://www.overleaf.com',
- },
- references: {
- url: 'https://www.overleaf.com',
- },
- v1: {
- url: 'https://www.overleaf.com',
- },
- }
- })
- it('should return true', function () {
- expect(this.Features.hasFeature('affiliations')).to.be.true
- expect(this.Features.hasFeature('analytics')).to.be.true
- expect(this.Features.hasFeature('saas')).to.be.true
- expect(this.Features.hasFeature('references')).to.be.true
- expect(this.Features.hasFeature('registration')).to.be.true
- })
- it('should return false', function () {
- expect(this.Features.hasFeature('link-url')).to.be.false
- expect(this.Features.hasFeature('git-bridge')).to.be.false
- expect(this.Features.hasFeature('github-sync')).to.be.false
- expect(this.Features.hasFeature('homepage')).to.be.false
- expect(this.Features.hasFeature('oauth')).to.be.false
- expect(this.Features.hasFeature('saml')).to.be.false
- expect(this.Features.hasFeature('templates-server-pro')).to.be.false
- })
- describe('with all other settings flags', function () {
- beforeEach(function () {
- this.settings.enableHomepage = true
- this.settings.enableGitBridge = true
- this.settings.enableGithubSync = true
- this.settings.enableSaml = true
- this.settings.oauth = true
- this.settings.enabledLinkedFileTypes = ['url', 'project_file']
- })
- it('should return true or return value', function () {
- expect(this.Features.hasFeature('link-url')).to.be.true
- expect(this.Features.hasFeature('affiliations')).to.be.true
- expect(this.Features.hasFeature('analytics')).to.be.true
- expect(this.Features.hasFeature('github-sync')).to.be.true
- expect(this.Features.hasFeature('git-bridge')).to.be.true
- expect(this.Features.hasFeature('homepage')).to.be.true
- expect(this.Features.hasFeature('link-url')).to.be.true
- expect(this.Features.hasFeature('oauth')).to.be.true
- expect(this.Features.hasFeature('saas')).to.be.true
- expect(this.Features.hasFeature('references')).to.be.true
- expect(this.Features.hasFeature('registration')).to.be.true
- expect(this.Features.hasFeature('saml')).to.be.true
- })
- it('should return false', function () {
- expect(this.Features.hasFeature('templates-server-pro')).to.be
- .false
- })
- })
- })
- })
- })
- })
-})
diff --git a/services/web/test/unit/src/infrastructure/GeoIpLookup.test.mjs b/services/web/test/unit/src/infrastructure/GeoIpLookup.test.mjs
index 794e5c706c..858025be2d 100644
--- a/services/web/test/unit/src/infrastructure/GeoIpLookup.test.mjs
+++ b/services/web/test/unit/src/infrastructure/GeoIpLookup.test.mjs
@@ -1,5 +1,5 @@
import { assert, describe, beforeEach, it, vi, expect } from 'vitest'
-import path from 'path'
+import path from 'node:path'
import sinon from 'sinon'
const modulePath = path.join(