From d5296783a42996e4d62220067c290165bd8da37e Mon Sep 17 00:00:00 2001 From: Andrew Rumble Date: Tue, 7 Oct 2025 11:52:28 +0100 Subject: [PATCH] Convert to ESM GitOrigin-RevId: bc407a128b024792a65b62c2eaeacefa48ecfe8d --- .../src/infrastructure/BodyParserWrapper.mjs | 6 +- services/web/app/src/infrastructure/CSP.mjs | 18 +- .../app/src/infrastructure/CookieMetrics.mjs | 6 +- services/web/app/src/infrastructure/Csrf.mjs | 43 +--- .../src/infrastructure/CustomSessionStore.mjs | 23 +- .../app/src/infrastructure/ExpressLocals.mjs | 67 ++--- .../app/src/infrastructure/FrontEndUser.mjs | 6 +- .../app/src/infrastructure/GeoIpLookup.mjs | 8 +- .../app/src/infrastructure/JsonWebToken.mjs | 8 +- services/web/app/src/infrastructure/Keys.mjs | 2 +- .../src/infrastructure/LoggerSerializers.mjs | 2 +- .../app/src/infrastructure/QueueWorkers.mjs | 26 +- .../src/infrastructure/RedirectManager.mjs | 9 +- .../web/app/src/infrastructure/Server.mjs | 6 +- .../SessionAutostartMiddleware.mjs | 6 +- .../src/infrastructure/SiteAdminHandler.mjs | 20 +- .../app/src/infrastructure/Translations.mjs | 24 +- .../UnsupportedBrowserMiddleware.mjs | 10 +- .../unit/src/infrastructure/Csrf.test.mjs | 233 ++++++++---------- .../src/infrastructure/GeoIpLookup.test.mjs | 129 +++++----- .../src/infrastructure/Translations.test.mjs | 83 ++++--- 21 files changed, 350 insertions(+), 385 deletions(-) diff --git a/services/web/app/src/infrastructure/BodyParserWrapper.mjs b/services/web/app/src/infrastructure/BodyParserWrapper.mjs index 544a7f22a4..b141a8bd4b 100644 --- a/services/web/app/src/infrastructure/BodyParserWrapper.mjs +++ b/services/web/app/src/infrastructure/BodyParserWrapper.mjs @@ -1,5 +1,5 @@ -const bodyParser = require('body-parser') -const HttpErrorHandler = require('../Features/Errors/HttpErrorHandler') +import bodyParser from 'body-parser' +import HttpErrorHandler from '../Features/Errors/HttpErrorHandler.js' function isBodyParserError(nextArg) { if (nextArg instanceof Error) { @@ -29,7 +29,7 @@ const wrapBodyParser = method => opts => { } } -module.exports = { +export default { urlencoded: wrapBodyParser('urlencoded'), json: wrapBodyParser('json'), } diff --git a/services/web/app/src/infrastructure/CSP.mjs b/services/web/app/src/infrastructure/CSP.mjs index 423154d623..eb16282c9d 100644 --- a/services/web/app/src/infrastructure/CSP.mjs +++ b/services/web/app/src/infrastructure/CSP.mjs @@ -1,7 +1,7 @@ -const crypto = require('crypto') -const path = require('path') +import crypto from 'node:crypto' +import path from 'node:path' -module.exports = function ({ +export default function ({ reportUri, reportPercentage, reportOnly = false, @@ -57,7 +57,7 @@ module.exports = function ({ } } -const buildDefaultPolicy = (reportUri, styleSrc) => { +export const buildDefaultPolicy = (reportUri, styleSrc) => { const directives = [ `base-uri 'none'`, // forbid setting a "base" element `default-src 'none'`, // forbid loading anything from a "src" attribute @@ -104,7 +104,7 @@ const buildViewPolicy = ( return directives.join('; ') } -const webRoot = path.resolve(__dirname, '..', '..', '..') +const webRoot = path.resolve(import.meta.dirname, '..', '..', '..') // build the view path relative to the web root function relativeViewPath(view) { @@ -113,7 +113,7 @@ function relativeViewPath(view) { : path.join('app', 'views', view) } -function removeCSPHeaders(res) { +export function removeCSPHeaders(res) { res.removeHeader('Content-Security-Policy') res.removeHeader('Content-Security-Policy-Report-Only') } @@ -122,13 +122,9 @@ function removeCSPHeaders(res) { * WARNING: allowing inline styles can open a security hole; * this is intended only for use in specific circumstances, such as Safari's built-in PDF viewer. */ -function allowUnsafeInlineStyles(res) { +export function allowUnsafeInlineStyles(res) { res.set( 'Content-Security-Policy', buildDefaultPolicy(undefined, "'unsafe-inline'") ) } - -module.exports.buildDefaultPolicy = buildDefaultPolicy -module.exports.removeCSPHeaders = removeCSPHeaders -module.exports.allowUnsafeInlineStyles = allowUnsafeInlineStyles diff --git a/services/web/app/src/infrastructure/CookieMetrics.mjs b/services/web/app/src/infrastructure/CookieMetrics.mjs index 9e35b51b6f..1755043a07 100644 --- a/services/web/app/src/infrastructure/CookieMetrics.mjs +++ b/services/web/app/src/infrastructure/CookieMetrics.mjs @@ -1,5 +1,5 @@ -const Settings = require('@overleaf/settings') -const metrics = require('@overleaf/metrics') +import Settings from '@overleaf/settings' +import metrics from '@overleaf/metrics' /** * Middleware function to record session cookie metrics. This allows us to @@ -27,4 +27,4 @@ function middleware(req, res, next) { next() } -module.exports = { middleware } +export default { middleware } diff --git a/services/web/app/src/infrastructure/Csrf.mjs b/services/web/app/src/infrastructure/Csrf.mjs index 71fb3ac69c..2192db149f 100644 --- a/services/web/app/src/infrastructure/Csrf.mjs +++ b/services/web/app/src/infrastructure/Csrf.mjs @@ -1,22 +1,9 @@ -/* eslint-disable - max-len, - no-return-assign, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ +import csurf from 'csurf' +import { promisify } from 'node:util' +import Settings from '@overleaf/settings' +import logger from '@overleaf/logger' -const csurf = require('csurf') const csrf = csurf() -const { promisify } = require('util') -const Settings = require('@overleaf/settings') -const logger = require('@overleaf/logger') // Wrapper for `csurf` middleware that provides a list of routes that can be excluded from csrf checks. // @@ -53,7 +40,7 @@ class Csrf { if (!this.excluded_routes[route]) { this.excluded_routes[route] = {} } - return (this.excluded_routes[route][method] = 1) + this.excluded_routes[route][method] = 1 } middleware(req, res, next) { @@ -62,21 +49,17 @@ class Csrf { // token' error from csurf and continue on... // check whether the request method is excluded for the specified route - if ( - (this.excluded_routes[req.path] != null - ? this.excluded_routes[req.path][req.method] - : undefined) === 1 - ) { + if (this.excluded_routes[req.path]?.[req.method] === 1) { // ignore the error if it's due to a bad csrf token, and continue - return csrf(req, res, err => { + csrf(req, res, err => { if (err && err.code !== 'EBADCSRFTOKEN') { - return next(err) + next(err) } else { - return next() + next() } }) } else { - return csrf(req, res, next) + csrf(req, res, next) } } @@ -85,7 +68,7 @@ class Csrf { if (cb == null) { cb = function (valid) {} } - return csrf(req, null, err => cb(err)) + csrf(req, null, err => cb(err)) } static validateToken(token, session, cb) { @@ -102,7 +85,7 @@ class Csrf { method: 'POST', session, } - return Csrf.validateRequest(req, cb) + Csrf.validateRequest(req, cb) } } @@ -111,4 +94,4 @@ Csrf.promises = { validateToken: promisify(Csrf.validateToken), } -module.exports = Csrf +export default Csrf diff --git a/services/web/app/src/infrastructure/CustomSessionStore.mjs b/services/web/app/src/infrastructure/CustomSessionStore.mjs index 5c8110573e..f86c933710 100644 --- a/services/web/app/src/infrastructure/CustomSessionStore.mjs +++ b/services/web/app/src/infrastructure/CustomSessionStore.mjs @@ -1,10 +1,11 @@ -const session = require('express-session') -const RedisStore = require('connect-redis')(session) -const metrics = require('@overleaf/metrics') -const logger = require('@overleaf/logger') -const Settings = require('@overleaf/settings') -const SessionManager = require('../Features/Authentication/SessionManager') -const Metrics = require('@overleaf/metrics') +import session from 'express-session' +import RedisStoreFactory from 'connect-redis' +import logger from '@overleaf/logger' +import Settings from '@overleaf/settings' +import SessionManager from '../Features/Authentication/SessionManager.js' +import Metrics from '@overleaf/metrics' + +const RedisStore = RedisStoreFactory(session) const MAX_SESSION_SIZE_THRESHOLD = 4096 @@ -37,14 +38,14 @@ class CustomSessionStore extends RedisStore { } const size = sess ? JSON.stringify(sess).length : 0 // record the number of redis session operations - metrics.inc('session.store.count', 1, { + Metrics.inc('session.store.count', 1, { method, type, status: size > MAX_SESSION_SIZE_THRESHOLD ? 'oversize' : 'normal', }) // record the redis session bandwidth for get/set operations if (method === 'get' || method === 'set') { - metrics.count('session.store.bytes', size, { method, type }) + Metrics.count('session.store.bytes', size, { method, type }) } // log the largest anonymous session seen so far if (type === 'anonymous' && size > CustomSessionStore.largestSessionSize) { @@ -153,7 +154,7 @@ class CustomSetRedisClient { set(args, cb) { args.push(this.#flag) this.#client.set(args, (err, ok) => { - metrics.inc('session.store.set', 1, { + Metrics.inc('session.store.set', 1, { path: this.#flag, status: err ? 'error' : ok ? 'success' : 'failure', }) @@ -162,4 +163,4 @@ class CustomSetRedisClient { } } -module.exports = CustomSessionStore +export default CustomSessionStore diff --git a/services/web/app/src/infrastructure/ExpressLocals.mjs b/services/web/app/src/infrastructure/ExpressLocals.mjs index 0047cd3d0d..b5cec630cb 100644 --- a/services/web/app/src/infrastructure/ExpressLocals.mjs +++ b/services/web/app/src/infrastructure/ExpressLocals.mjs @@ -1,36 +1,43 @@ -const logger = require('@overleaf/logger') -const Metrics = require('@overleaf/metrics') -const Settings = require('@overleaf/settings') -const _ = require('lodash') -const { URL } = require('url') -const Path = require('path') -const moment = require('moment') -const { fetchJson } = require('@overleaf/fetch-utils') -const contentDisposition = require('content-disposition') -const Features = require('./Features') -const SessionManager = require('../Features/Authentication/SessionManager') -const PackageVersions = require('./PackageVersions') -const Modules = require('./Modules') -const Errors = require('../Features/Errors/Errors') -const { +import logger from '@overleaf/logger' +import Metrics from '@overleaf/metrics' +import Settings from '@overleaf/settings' +import _ from 'lodash' +import { URL } from 'node:url' +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 SessionManager from '../Features/Authentication/SessionManager.js' +import PackageVersions from './PackageVersions.js' +import Modules from './Modules.js' +import Errors from '../Features/Errors/Errors.js' + +import { canRedirectToAdminDomain, hasAdminAccess, useAdminCapabilities, useHasAdminCapability, -} = require('../Features/Helpers/AdminAuthorizationHelper') -const { - addOptionalCleanupHandlerAfterDrainingConnections, -} = require('./GracefulShutdown') -const { sanitizeSessionUserForFrontEnd } = require('./FrontEndUser') +} from '../Features/Helpers/AdminAuthorizationHelper.js' + +import { addOptionalCleanupHandlerAfterDrainingConnections } from './GracefulShutdown.js' +import { sanitizeSessionUserForFrontEnd } from './FrontEndUser.mjs' +import { expressify } from '@overleaf/promise-utils' const IEEE_BRAND_ID = Settings.ieeeBrandId let webpackManifest -function loadManifest() { +async function loadManifest() { switch (process.env.NODE_ENV) { case 'production': + /* eslint-disable import/no-unresolved */ // Only load webpack manifest file in production. - webpackManifest = require('../../../public/manifest.json') + webpackManifest = ( + await import('../../../public/manifest.json', { + with: { type: 'json' }, + }) + ).default + /* eslint-enable import/no-unresolved */ break case 'development': { // In dev, fetch the manifest from the webpack container. @@ -76,8 +83,8 @@ function getWebpackAssets(entrypoint, section) { return webpackManifest.entrypoints[entrypoint].assets[section] || [] } -module.exports = function (webRouter, privateApiRouter, publicApiRouter) { - loadManifest() +export default async function (webRouter, privateApiRouter, publicApiRouter) { + await loadManifest() if (process.env.NODE_ENV === 'development') { // In the dev-env, delay requests until we fetched the manifest once. webRouter.use(function (req, res, next) { @@ -253,10 +260,14 @@ module.exports = function (webRouter, privateApiRouter, publicApiRouter) { next() }) - webRouter.use(function (req, res, next) { - res.locals.StringHelper = require('../Features/Helpers/StringHelper') - next() - }) + webRouter.use( + expressify(async function (req, res, next) { + res.locals.StringHelper = ( + await import('../Features/Helpers/StringHelper.js') + ).default + next() + }) + ) webRouter.use(function (req, res, next) { res.locals.csrfToken = req != null ? req.csrfToken() : undefined diff --git a/services/web/app/src/infrastructure/FrontEndUser.mjs b/services/web/app/src/infrastructure/FrontEndUser.mjs index 5a4af9868c..85a187b766 100644 --- a/services/web/app/src/infrastructure/FrontEndUser.mjs +++ b/services/web/app/src/infrastructure/FrontEndUser.mjs @@ -1,4 +1,4 @@ -function sanitizeSessionUserForFrontEnd(sessionUser) { +export function sanitizeSessionUserForFrontEnd(sessionUser) { if (sessionUser != null) { return { email: sessionUser.email, @@ -9,7 +9,3 @@ function sanitizeSessionUserForFrontEnd(sessionUser) { return null } - -module.exports = { - sanitizeSessionUserForFrontEnd, -} diff --git a/services/web/app/src/infrastructure/GeoIpLookup.mjs b/services/web/app/src/infrastructure/GeoIpLookup.mjs index ce0da00973..4c7ff12466 100644 --- a/services/web/app/src/infrastructure/GeoIpLookup.mjs +++ b/services/web/app/src/infrastructure/GeoIpLookup.mjs @@ -1,6 +1,6 @@ -const settings = require('@overleaf/settings') -const logger = require('@overleaf/logger') -const { fetchJson } = require('@overleaf/fetch-utils') +import settings from '@overleaf/settings' +import logger from '@overleaf/logger' +import { fetchJson } from '@overleaf/fetch-utils' const DEFAULT_CURRENCY_CODE = 'USD' @@ -104,7 +104,7 @@ async function getCurrencyCode(ip) { return { currencyCode, countryCode } } -module.exports = { +export default { isValidCurrencyParam, promises: { getDetails, diff --git a/services/web/app/src/infrastructure/JsonWebToken.mjs b/services/web/app/src/infrastructure/JsonWebToken.mjs index 8ce7c78ba4..f85d77a747 100644 --- a/services/web/app/src/infrastructure/JsonWebToken.mjs +++ b/services/web/app/src/infrastructure/JsonWebToken.mjs @@ -1,6 +1,6 @@ -const { callbackify, promisify } = require('util') -const JWT = require('jsonwebtoken') -const Settings = require('@overleaf/settings') +import { callbackify, promisify } from 'node:util' +import JWT from 'jsonwebtoken' +import Settings from '@overleaf/settings' const jwtSign = promisify(JWT.sign) @@ -20,7 +20,7 @@ function getDecoded(token) { return decoded } -module.exports = { +export default { sign: callbackify(sign), getDecoded, promises: { diff --git a/services/web/app/src/infrastructure/Keys.mjs b/services/web/app/src/infrastructure/Keys.mjs index cd177b90bc..8f737bc394 100644 --- a/services/web/app/src/infrastructure/Keys.mjs +++ b/services/web/app/src/infrastructure/Keys.mjs @@ -1,6 +1,6 @@ // TODO: This file was created by bulk-decaffeinate. // Sanity-check the conversion and remove this comment. -module.exports = { +export default { queue: { web_to_tpds_http_requests: 'web_to_tpds_http_requests', tpds_to_web_http_requests: 'tpds_to_web_http_requests', diff --git a/services/web/app/src/infrastructure/LoggerSerializers.mjs b/services/web/app/src/infrastructure/LoggerSerializers.mjs index 09d274aa82..4d5d6cf6a8 100644 --- a/services/web/app/src/infrastructure/LoggerSerializers.mjs +++ b/services/web/app/src/infrastructure/LoggerSerializers.mjs @@ -6,7 +6,7 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -module.exports = { +export default { user(user) { if (user == null) { return null diff --git a/services/web/app/src/infrastructure/QueueWorkers.mjs b/services/web/app/src/infrastructure/QueueWorkers.mjs index 8cc8612071..ff6eabee5f 100644 --- a/services/web/app/src/infrastructure/QueueWorkers.mjs +++ b/services/web/app/src/infrastructure/QueueWorkers.mjs @@ -1,16 +1,18 @@ -const Features = require('./Features') -const Queues = require('./Queues') -const UserOnboardingEmailManager = require('../Features/User/UserOnboardingEmailManager') -const UserPostRegistrationAnalyticsManager = require('../Features/User/UserPostRegistrationAnalyticsManager') -const FeaturesUpdater = require('../Features/Subscription/FeaturesUpdater') -const { +import Features from './Features.js' +import Queues from './Queues.js' +import UserOnboardingEmailManager from '../Features/User/UserOnboardingEmailManager.js' +import UserPostRegistrationAnalyticsManager from '../Features/User/UserPostRegistrationAnalyticsManager.js' +import FeaturesUpdater from '../Features/Subscription/FeaturesUpdater.js' + +import { addOptionalCleanupHandlerBeforeStoppingTraffic, addRequiredCleanupHandlerBeforeDrainingConnections, -} = require('./GracefulShutdown') -const EmailHandler = require('../Features/Email/EmailHandler') -const logger = require('@overleaf/logger') -const OError = require('@overleaf/o-error') -const Modules = require('./Modules') +} from './GracefulShutdown.js' + +import EmailHandler from '../Features/Email/EmailHandler.js' +import logger from '@overleaf/logger' +import OError from '@overleaf/o-error' +import Modules from './Modules.js' /** * @typedef {{ @@ -124,4 +126,4 @@ function registerCleanup(queue) { // Disconnect from redis is scheduled in queue setup. } -module.exports = { start, registerQueue } +export default { start, registerQueue } diff --git a/services/web/app/src/infrastructure/RedirectManager.mjs b/services/web/app/src/infrastructure/RedirectManager.mjs index 2a11af8d6b..1fa608b9c2 100644 --- a/services/web/app/src/infrastructure/RedirectManager.mjs +++ b/services/web/app/src/infrastructure/RedirectManager.mjs @@ -12,11 +12,12 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let RedirectManager -const settings = require('@overleaf/settings') -const { URL } = require('url') +import settings from '@overleaf/settings' +import { URL } from 'node:url' -module.exports = RedirectManager = { +let RedirectManager + +export default RedirectManager = { apply(webRouter) { return (() => { const result = [] diff --git a/services/web/app/src/infrastructure/Server.mjs b/services/web/app/src/infrastructure/Server.mjs index 62e0a99288..af1ed0faa2 100644 --- a/services/web/app/src/infrastructure/Server.mjs +++ b/services/web/app/src/infrastructure/Server.mjs @@ -3,7 +3,7 @@ import Settings from '@overleaf/settings' import logger from '@overleaf/logger' import metrics from '@overleaf/metrics' import Validation from './Validation.js' -import csp from './CSP.mjs' +import csp, { removeCSPHeaders } from './CSP.mjs' import Router from '../router.mjs' import helmet from 'helmet' import UserSessionsRedis from '../Features/User/UserSessionsRedis.js' @@ -125,7 +125,7 @@ webRouter.use( fileURLToPath(new URL('../../../public', import.meta.url)), { maxAge: STATIC_CACHE_AGE, - setHeaders: csp.removeCSPHeaders, + setHeaders: removeCSPHeaders, } ) ) @@ -257,7 +257,7 @@ if (Settings.cookieRollingSession) { } webRouter.use(ReferalConnect.use) -expressLocals(webRouter, privateApiRouter, publicApiRouter) +await expressLocals(webRouter, privateApiRouter, publicApiRouter) webRouter.use(SessionAutostartMiddleware.invokeCallbackMiddleware) webRouter.use(function checkIfSiteClosed(req, res, next) { diff --git a/services/web/app/src/infrastructure/SessionAutostartMiddleware.mjs b/services/web/app/src/infrastructure/SessionAutostartMiddleware.mjs index eaf6f587cf..a58b92b013 100644 --- a/services/web/app/src/infrastructure/SessionAutostartMiddleware.mjs +++ b/services/web/app/src/infrastructure/SessionAutostartMiddleware.mjs @@ -1,5 +1,5 @@ -const Settings = require('@overleaf/settings') -const OError = require('@overleaf/o-error') +import Settings from '@overleaf/settings' +import OError from '@overleaf/o-error' const botUserAgents = [ 'kube-probe', @@ -125,4 +125,4 @@ class SessionAutostartMiddleware { } } -module.exports = SessionAutostartMiddleware +export default SessionAutostartMiddleware diff --git a/services/web/app/src/infrastructure/SiteAdminHandler.mjs b/services/web/app/src/infrastructure/SiteAdminHandler.mjs index cd6df2a49b..fca93a956b 100644 --- a/services/web/app/src/infrastructure/SiteAdminHandler.mjs +++ b/services/web/app/src/infrastructure/SiteAdminHandler.mjs @@ -1,13 +1,15 @@ -const logger = require('@overleaf/logger') -const settings = require('@overleaf/settings') -const fs = require('fs') -const { +import logger from '@overleaf/logger' +import settings from '@overleaf/settings' +import fs from 'node:fs' + +import { addOptionalCleanupHandlerAfterDrainingConnections, addRequiredCleanupHandlerBeforeDrainingConnections, -} = require('./GracefulShutdown') -const Features = require('./Features') -const UserHandler = require('../Features/User/UserHandler') -const metrics = require('@overleaf/metrics') +} from './GracefulShutdown.js' + +import Features from './Features.js' +import UserHandler from '../Features/User/UserHandler.js' +import metrics from '@overleaf/metrics' // Monitor a site maintenance file (e.g. /etc/site_status) periodically and // close the site if the file contents contain the string "closed". @@ -56,7 +58,7 @@ function publishActiveUsersMetric() { .catch(error => logger.error({ error }, 'error counting active users')) } -module.exports = { +export default { initialise() { if (settings.enabledServices.includes('web') && statusFile) { logger.debug( diff --git a/services/web/app/src/infrastructure/Translations.mjs b/services/web/app/src/infrastructure/Translations.mjs index 9c2cc79ba7..ed9067a6e8 100644 --- a/services/web/app/src/infrastructure/Translations.mjs +++ b/services/web/app/src/infrastructure/Translations.mjs @@ -1,12 +1,12 @@ -const i18n = require('i18next') -const fsBackend = require('i18next-fs-backend') -const middleware = require('i18next-http-middleware') -const path = require('path') -const Settings = require('@overleaf/settings') -const { URL } = require('url') -const pug = require('pug-runtime') -const logger = require('@overleaf/logger') -const SafeHTMLSubstitution = require('../Features/Helpers/SafeHTMLSubstitution') +import i18n from 'i18next' +import fsBackend from 'i18next-fs-backend' +import middleware from 'i18next-http-middleware' +import path from 'node:path' +import Settings from '@overleaf/settings' +import { URL } from 'node:url' +import pug from 'pug-runtime' +import logger from '@overleaf/logger' +import SafeHTMLSubstitution from '../Features/Helpers/SafeHTMLSubstitution.js' const fallbackLanguageCode = Settings.i18n.defaultLng || 'en' const availableLanguageCodes = [] @@ -35,7 +35,7 @@ if (!availableLanguageCodes.includes(fallbackLanguageCode)) { if (process.argv.includes('--watch-locales')) { // Dummy imports for setting up watching of locales files. for (const lngCode of availableLanguageCodes) { - require(`../../../locales/${lngCode}.json`) + await import(`../../../locales/${lngCode}.json`, { with: { type: 'json' } }) } } @@ -44,7 +44,7 @@ i18n .use(middleware.LanguageDetector) .init({ backend: { - loadPath: path.join(__dirname, '../../../locales/__lng__.json'), + loadPath: path.join(import.meta.dirname, '../../../locales/__lng__.json'), }, // still using the v3 plural suffixes @@ -140,7 +140,7 @@ function setLangBasedOnDomainMiddleware(req, res, next) { // in direct usage i18n.translate = i18n.t -module.exports = { +export default { i18nMiddleware: middleware.handle(i18n), setLangBasedOnDomainMiddleware, i18n, diff --git a/services/web/app/src/infrastructure/UnsupportedBrowserMiddleware.mjs b/services/web/app/src/infrastructure/UnsupportedBrowserMiddleware.mjs index c145c9f468..2151e64cd0 100644 --- a/services/web/app/src/infrastructure/UnsupportedBrowserMiddleware.mjs +++ b/services/web/app/src/infrastructure/UnsupportedBrowserMiddleware.mjs @@ -1,7 +1,7 @@ -const Bowser = require('bowser') -const Settings = require('@overleaf/settings') -const Url = require('url') -const { getSafeRedirectPath } = require('../Features/Helpers/UrlHelper') +import Bowser from 'bowser' +import Settings from '@overleaf/settings' +import Url from 'node:url' +import { getSafeRedirectPath } from '../Features/Helpers/UrlHelper.js' function unsupportedBrowserMiddleware(req, res, next) { if (!Settings.unsupportedBrowsers) return next() @@ -44,7 +44,7 @@ function renderUnsupportedBrowserPage(req, res) { res.render('general/unsupported-browser', { fromURL }) } -module.exports = { +export default { renderUnsupportedBrowserPage, unsupportedBrowserMiddleware, } diff --git a/services/web/test/unit/src/infrastructure/Csrf.test.mjs b/services/web/test/unit/src/infrastructure/Csrf.test.mjs index 4bcb213c4f..554170bd66 100644 --- a/services/web/test/unit/src/infrastructure/Csrf.test.mjs +++ b/services/web/test/unit/src/infrastructure/Csrf.test.mjs @@ -1,192 +1,161 @@ -/* eslint-disable - max-len, - no-return-assign, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const { assert, expect } = require('chai') -const sinon = require('sinon') -const modulePath = '../../../../app/src/infrastructure/Csrf.js' -const SandboxedModule = require('sandboxed-module') +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import sinon from 'sinon' +const modulePath = '../../../../app/src/infrastructure/Csrf.mjs' describe('Csrf', function () { - beforeEach(function () { - this.csurf_csrf = sinon + beforeEach(async function (ctx) { + ctx.csurf_csrf = sinon .stub() - .callsArgWith(2, (this.err = { code: 'EBADCSRFTOKEN' })) - this.Csrf = SandboxedModule.require(modulePath, { - requires: { - csurf: sinon.stub().returns(this.csurf_csrf), - }, - }) - this.csrf = new this.Csrf() - this.next = sinon.stub() - this.path = '/foo/bar' - this.req = { - path: this.path, + .callsArgWith(2, (ctx.err = { code: 'EBADCSRFTOKEN' })) + + vi.doMock('csurf', () => ({ + default: sinon.stub().returns(ctx.csurf_csrf), + })) + + ctx.Csrf = (await import(modulePath)).default + ctx.csrf = new ctx.Csrf() + ctx.next = sinon.stub() + ctx.path = '/foo/bar' + ctx.req = { + path: ctx.path, method: 'POST', } - return (this.res = {}) + ctx.res = {} }) describe('the middleware', function () { describe('when there are no excluded routes', function () { - it('passes the csrf error on', function () { - this.csrf.middleware(this.req, this.res, this.next) - return expect(this.next.calledWith(this.err)).to.equal(true) + it('passes the csrf error on', function (ctx) { + ctx.csrf.middleware(ctx.req, ctx.res, ctx.next) + return expect(ctx.next.calledWith(ctx.err)).to.equal(true) }) }) describe('when the route is excluded', function () { - it('does not pass the csrf error on', function () { - this.csrf.disableDefaultCsrfProtection(this.path, 'POST') - this.csrf.middleware(this.req, this.res, this.next) - return expect(this.next.calledWith(this.err)).to.equal(false) + it('does not pass the csrf error on', function (ctx) { + ctx.csrf.disableDefaultCsrfProtection(ctx.path, 'POST') + ctx.csrf.middleware(ctx.req, ctx.res, ctx.next) + return expect(ctx.next.calledWith(ctx.err)).to.equal(false) }) }) describe('when there is a partial route match', function () { - it('passes the csrf error on when the match is too short', function () { - this.csrf.disableDefaultCsrfProtection('/foo', 'POST') - this.csrf.middleware(this.req, this.res, this.next) - return expect(this.next.calledWith(this.err)).to.equal(true) + it('passes the csrf error on when the match is too short', function (ctx) { + ctx.csrf.disableDefaultCsrfProtection('/foo', 'POST') + ctx.csrf.middleware(ctx.req, ctx.res, ctx.next) + return expect(ctx.next.calledWith(ctx.err)).to.equal(true) }) - it('passes the csrf error on when the match is too long', function () { - this.csrf.disableDefaultCsrfProtection('/foo/bar/baz', 'POST') - this.csrf.middleware(this.req, this.res, this.next) - return expect(this.next.calledWith(this.err)).to.equal(true) + it('passes the csrf error on when the match is too long', function (ctx) { + ctx.csrf.disableDefaultCsrfProtection('/foo/bar/baz', 'POST') + ctx.csrf.middleware(ctx.req, ctx.res, ctx.next) + return expect(ctx.next.calledWith(ctx.err)).to.equal(true) }) }) describe('when there are multiple exclusions', function () { - it('does not pass the csrf error on when the match is present', function () { - this.csrf.disableDefaultCsrfProtection(this.path, 'POST') - this.csrf.disableDefaultCsrfProtection('/test', 'POST') - this.csrf.disableDefaultCsrfProtection('/a/b/c', 'POST') - this.csrf.middleware(this.req, this.res, this.next) - return expect(this.next.calledWith(this.err)).to.equal(false) + it('does not pass the csrf error on when the match is present', function (ctx) { + ctx.csrf.disableDefaultCsrfProtection(ctx.path, 'POST') + ctx.csrf.disableDefaultCsrfProtection('/test', 'POST') + ctx.csrf.disableDefaultCsrfProtection('/a/b/c', 'POST') + ctx.csrf.middleware(ctx.req, ctx.res, ctx.next) + return expect(ctx.next.calledWith(ctx.err)).to.equal(false) }) - it('passes the csrf error on when the match is not present', function () { - this.csrf.disableDefaultCsrfProtection('/url', 'POST') - this.csrf.disableDefaultCsrfProtection('/test', 'POST') - this.csrf.disableDefaultCsrfProtection('/a/b/c', 'POST') - this.csrf.middleware(this.req, this.res, this.next) - return expect(this.next.calledWith(this.err)).to.equal(true) + it('passes the csrf error on when the match is not present', function (ctx) { + ctx.csrf.disableDefaultCsrfProtection('/url', 'POST') + ctx.csrf.disableDefaultCsrfProtection('/test', 'POST') + ctx.csrf.disableDefaultCsrfProtection('/a/b/c', 'POST') + ctx.csrf.middleware(ctx.req, ctx.res, ctx.next) + return expect(ctx.next.calledWith(ctx.err)).to.equal(true) }) }) describe('when the method does not match', function () { - it('passes the csrf error on', function () { - this.csrf.disableDefaultCsrfProtection(this.path, 'POST') - this.req.method = 'GET' - this.csrf.middleware(this.req, this.res, this.next) - return expect(this.next.calledWith(this.err)).to.equal(true) + it('passes the csrf error on', function (ctx) { + ctx.csrf.disableDefaultCsrfProtection(ctx.path, 'POST') + ctx.req.method = 'GET' + ctx.csrf.middleware(ctx.req, ctx.res, ctx.next) + return expect(ctx.next.calledWith(ctx.err)).to.equal(true) }) }) describe('when the route is excluded, but the error is not a bad-csrf-token error', function () { - it('passes the error on', function () { - let err - this.Csrf = SandboxedModule.require(modulePath, { - globals: { - console, - }, - requires: { - csurf: (this.csurf = sinon - .stub() - .returns( - (this.csurf_csrf = sinon - .stub() - .callsArgWith(2, (err = { code: 'EOTHER' }))) - )), - }, - }) - this.csrf = new this.Csrf() - this.csrf.disableDefaultCsrfProtection(this.path, 'POST') - this.csrf.middleware(this.req, this.res, this.next) - expect(this.next.calledWith(err)).to.equal(true) - return expect(this.next.calledWith(this.err)).to.equal(false) + it('passes the error on', async function (ctx) { + const err = { code: 'EOTHER' } + + ctx.csurf_csrf.callsArgWith(2, err) + + const csrf = new ctx.Csrf() + csrf.disableDefaultCsrfProtection(ctx.path, 'POST') + csrf.middleware(ctx.req, ctx.res, ctx.next) + expect(ctx.next.calledWith(err)).to.equal(true) + expect(ctx.next.calledWith(ctx.err)).to.equal(false) }) }) }) describe('validateRequest', function () { describe('when the request is invalid', function () { - it('calls the callback with error', function () { - this.cb = sinon.stub() - this.Csrf.validateRequest(this.req, this.cb) - return expect(this.cb.calledWith(this.err)).to.equal(true) + it('calls the callback with error', function (ctx) { + ctx.cb = sinon.stub() + ctx.Csrf.validateRequest(ctx.req, ctx.cb) + expect(ctx.cb.calledWith(ctx.err)).to.equal(true) }) }) describe('when the request is valid', function () { - it('calls the callback without an error', function () { - this.Csrf = SandboxedModule.require(modulePath, { - globals: { - console, - }, - requires: { - csurf: (this.csurf = sinon - .stub() - .returns((this.csurf_csrf = sinon.stub().callsArg(2)))), - }, - }) - this.cb = sinon.stub() - this.Csrf.validateRequest(this.req, this.cb) - return expect(this.cb.calledWith()).to.equal(true) + it('calls the callback without an error', async function (ctx) { + vi.doMock('csurf', () => ({ + default: (ctx.csurf = sinon + .stub() + .returns((ctx.csurf_csrf = sinon.stub().callsArg(2)))), + })) + + ctx.Csrf = (await import(modulePath)).default + ctx.cb = sinon.stub() + ctx.Csrf.validateRequest(ctx.req, ctx.cb) + expect(ctx.cb.calledWith()).to.equal(true) }) }) }) describe('validateToken', function () { describe('when the request is invalid', function () { - it('calls the callback with `false`', function () { - this.cb = sinon.stub() - this.Csrf.validateToken('token', {}, this.cb) - expect(this.cb.calledWith(this.err)).to.equal(true) + it('calls the callback with `false`', function (ctx) { + ctx.cb = sinon.stub() + ctx.Csrf.validateToken('token', {}, ctx.cb) + expect(ctx.cb.calledWith(ctx.err)).to.equal(true) }) }) describe('when the request is valid', function () { - it('calls the callback with `true`', function () { - this.Csrf = SandboxedModule.require(modulePath, { - globals: { - console, - }, - requires: { - csurf: (this.csurf = sinon - .stub() - .returns((this.csurf_csrf = sinon.stub().callsArg(2)))), - }, - }) - this.cb = sinon.stub() - this.Csrf.validateToken('goodtoken', {}, this.cb) - return expect(this.cb.calledWith()).to.equal(true) + it('calls the callback with `true`', async function (ctx) { + vi.doMock('csurf', () => ({ + default: (ctx.csurf = sinon + .stub() + .returns((ctx.csurf_csrf = sinon.stub().callsArg(2)))), + })) + + ctx.Csrf = (await import(modulePath)).default + ctx.cb = sinon.stub() + ctx.Csrf.validateToken('goodtoken', {}, ctx.cb) + expect(ctx.cb.calledWith()).to.equal(true) }) }) describe('when there is no token', function () { - it('calls the callback with an error', function () { - this.Csrf = SandboxedModule.require(modulePath, { - globals: { - console, - }, - requires: { - csurf: (this.csurf = sinon - .stub() - .returns((this.csurf_csrf = sinon.stub().callsArg(2)))), - }, - }) - this.cb = sinon.stub() - this.Csrf.validateToken(null, {}, error => { + it('calls the callback with an error', async function (ctx) { + vi.doMock('csurf', () => ({ + default: (ctx.csurf = sinon + .stub() + .returns((ctx.csurf_csrf = sinon.stub().callsArg(2)))), + })) + + ctx.Csrf = (await import(modulePath)).default + ctx.cb = sinon.stub() + ctx.Csrf.validateToken(null, {}, error => { expect(error).to.exist }) }) diff --git a/services/web/test/unit/src/infrastructure/GeoIpLookup.test.mjs b/services/web/test/unit/src/infrastructure/GeoIpLookup.test.mjs index 5a70c9038e..794e5c706c 100644 --- a/services/web/test/unit/src/infrastructure/GeoIpLookup.test.mjs +++ b/services/web/test/unit/src/infrastructure/GeoIpLookup.test.mjs @@ -1,19 +1,18 @@ -const SandboxedModule = require('sandboxed-module') -const assert = require('assert') -const path = require('path') -const sinon = require('sinon') -const { expect } = require('chai') +import { assert, describe, beforeEach, it, vi, expect } from 'vitest' +import path from 'path' +import sinon from 'sinon' + const modulePath = path.join( - __dirname, + import.meta.dirname, '../../../../app/src/infrastructure/GeoIpLookup' ) describe('GeoIpLookup', function () { - beforeEach(function () { - this.ipAddress = '12.34.56.78' + beforeEach(async function (ctx) { + ctx.ipAddress = '12.34.56.78' - this.stubbedResponse = { - ip: this.ipAddress, + ctx.stubbedResponse = { + ip: ctx.ipAddress, country_code: 'GB', country_name: 'United Kingdom', region_code: 'H9', @@ -25,63 +24,67 @@ describe('GeoIpLookup', function () { metro_code: '', area_code: '', } - this.fetchUtils = { - fetchJson: sinon.stub().resolves(this.stubbedResponse), + ctx.fetchUtils = { + fetchJson: sinon.stub().resolves(ctx.stubbedResponse), } - this.settings = { + ctx.settings = { apis: { geoIpLookup: { url: 'http://lookup.com/', }, }, } - this.GeoIpLookup = SandboxedModule.require(modulePath, { - requires: { - '@overleaf/fetch-utils': this.fetchUtils, - '@overleaf/settings': this.settings, - }, - }) + + vi.doMock('@overleaf/fetch-utils', () => ({ + ...ctx.fetchUtils, + })) + + vi.doMock('@overleaf/settings', () => ({ + default: ctx.settings, + })) + + ctx.GeoIpLookup = (await import(modulePath)).default }) describe('isValidCurrencyParam', function () { - it('should reject invalid currency codes', function () { - expect(this.GeoIpLookup.isValidCurrencyParam('GBP')).to.equal(true) - expect(this.GeoIpLookup.isValidCurrencyParam('USD')).to.equal(true) - expect(this.GeoIpLookup.isValidCurrencyParam('AUD')).to.equal(true) - expect(this.GeoIpLookup.isValidCurrencyParam('EUR')).to.equal(true) - expect(this.GeoIpLookup.isValidCurrencyParam('SGD')).to.equal(true) - expect(this.GeoIpLookup.isValidCurrencyParam('WAT')).to.equal(false) - expect(this.GeoIpLookup.isValidCurrencyParam('NON')).to.equal(false) - expect(this.GeoIpLookup.isValidCurrencyParam('LOL')).to.equal(false) + it('should reject invalid currency codes', function (ctx) { + expect(ctx.GeoIpLookup.isValidCurrencyParam('GBP')).to.equal(true) + expect(ctx.GeoIpLookup.isValidCurrencyParam('USD')).to.equal(true) + expect(ctx.GeoIpLookup.isValidCurrencyParam('AUD')).to.equal(true) + expect(ctx.GeoIpLookup.isValidCurrencyParam('EUR')).to.equal(true) + expect(ctx.GeoIpLookup.isValidCurrencyParam('SGD')).to.equal(true) + expect(ctx.GeoIpLookup.isValidCurrencyParam('WAT')).to.equal(false) + expect(ctx.GeoIpLookup.isValidCurrencyParam('NON')).to.equal(false) + expect(ctx.GeoIpLookup.isValidCurrencyParam('LOL')).to.equal(false) }) }) describe('getDetails', function () { - beforeEach(function () { - this.fetchUtils.fetchJson.resolves(this.stubbedResponse) + beforeEach(function (ctx) { + ctx.fetchUtils.fetchJson.resolves(ctx.stubbedResponse) }) describe('async', function () { - it('should request the details using the ip', async function () { - await this.GeoIpLookup.promises.getDetails(this.ipAddress) - this.fetchUtils.fetchJson.should.have.been.calledWith( - new URL(this.settings.apis.geoIpLookup.url + this.ipAddress) + it('should request the details using the ip', async function (ctx) { + await ctx.GeoIpLookup.promises.getDetails(ctx.ipAddress) + ctx.fetchUtils.fetchJson.should.have.been.calledWith( + new URL(ctx.settings.apis.geoIpLookup.url + ctx.ipAddress) ) }) - it('should return the ip details', async function () { - const returnedDetails = await this.GeoIpLookup.promises.getDetails( - this.ipAddress + it('should return the ip details', async function (ctx) { + const returnedDetails = await ctx.GeoIpLookup.promises.getDetails( + ctx.ipAddress ) - assert.deepEqual(returnedDetails, this.stubbedResponse) + assert.deepEqual(returnedDetails, ctx.stubbedResponse) }) - it('should take the first ip in the string', async function () { - await this.GeoIpLookup.promises.getDetails( - ` ${this.ipAddress} 123.123.123.123 234.234.234.234` + it('should take the first ip in the string', async function (ctx) { + await ctx.GeoIpLookup.promises.getDetails( + ` ${ctx.ipAddress} 123.123.123.123 234.234.234.234` ) - this.fetchUtils.fetchJson.should.have.been.calledWith( - new URL(this.settings.apis.geoIpLookup.url + this.ipAddress) + ctx.fetchUtils.fetchJson.should.have.been.calledWith( + new URL(ctx.settings.apis.geoIpLookup.url + ctx.ipAddress) ) }) }) @@ -89,57 +92,57 @@ describe('GeoIpLookup', function () { describe('getCurrencyCode', function () { describe('async', function () { - it('should return GBP for GB country', async function () { + it('should return GBP for GB country', async function (ctx) { const { currencyCode, countryCode } = - await this.GeoIpLookup.promises.getCurrencyCode(this.ipAddress) + await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress) currencyCode.should.equal('GBP') countryCode.should.equal('GB') }) - it('should return GBP for gb country', async function () { - this.stubbedResponse.country_code = 'gb' + it('should return GBP for gb country', async function (ctx) { + ctx.stubbedResponse.country_code = 'gb' const { currencyCode, countryCode } = - await this.GeoIpLookup.promises.getCurrencyCode(this.ipAddress) + await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress) currencyCode.should.equal('GBP') countryCode.should.equal('GB') }) - it('should return USD for US', async function () { - this.stubbedResponse.country_code = 'US' + it('should return USD for US', async function (ctx) { + ctx.stubbedResponse.country_code = 'US' const { currencyCode, countryCode } = - await this.GeoIpLookup.promises.getCurrencyCode(this.ipAddress) + await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress) currencyCode.should.equal('USD') countryCode.should.equal('US') }) - it('should return EUR for DE', async function () { - this.stubbedResponse.country_code = 'DE' + it('should return EUR for DE', async function (ctx) { + ctx.stubbedResponse.country_code = 'DE' const { currencyCode, countryCode } = - await this.GeoIpLookup.promises.getCurrencyCode(this.ipAddress) + await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress) currencyCode.should.equal('EUR') countryCode.should.equal('DE') }) - it('should default to USD if there is an error', async function () { - this.fetchUtils.fetchJson.rejects(new Error('foo')) + it('should default to USD if there is an error', async function (ctx) { + ctx.fetchUtils.fetchJson.rejects(new Error('foo')) const { currencyCode, countryCode } = - await this.GeoIpLookup.promises.getCurrencyCode(this.ipAddress) + await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress) currencyCode.should.equal('USD') expect(countryCode).to.be.undefined }) - it('should default to USD if there are no details', async function () { - this.fetchUtils.fetchJson.resolves({}) + it('should default to USD if there are no details', async function (ctx) { + ctx.fetchUtils.fetchJson.resolves({}) const { currencyCode, countryCode } = - await this.GeoIpLookup.promises.getCurrencyCode(this.ipAddress) + await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress) currencyCode.should.equal('USD') expect(countryCode).to.be.undefined }) - it('should default to USD if there is no match for their country', async function () { - this.stubbedResponse.country_code = 'Non existant' + it('should default to USD if there is no match for their country', async function (ctx) { + ctx.stubbedResponse.country_code = 'Non existant' const { currencyCode, countryCode } = - await this.GeoIpLookup.promises.getCurrencyCode(this.ipAddress) + await ctx.GeoIpLookup.promises.getCurrencyCode(ctx.ipAddress) currencyCode.should.equal('USD') countryCode.should.equal('NON EXISTANT') }) diff --git a/services/web/test/unit/src/infrastructure/Translations.test.mjs b/services/web/test/unit/src/infrastructure/Translations.test.mjs index d957bd9885..b13b319a76 100644 --- a/services/web/test/unit/src/infrastructure/Translations.test.mjs +++ b/services/web/test/unit/src/infrastructure/Translations.test.mjs @@ -1,31 +1,38 @@ -const { expect } = require('chai') -const SandboxedModule = require('sandboxed-module') +import { describe, expect, it, vi } from 'vitest' -const MODULE_PATH = '../../../../app/src/infrastructure/Translations.js' +const MODULE_PATH = '../../../../app/src/infrastructure/Translations.mjs' describe('Translations', function () { let req, res, translations - function runMiddlewares(cb) { - translations.i18nMiddleware(req, res, () => { - translations.setLangBasedOnDomainMiddleware(req, res, cb) - }) + async function runMiddlewares(cb) { + return await new Promise((resolve, reject) => + translations.i18nMiddleware(req, res, () => { + translations.setLangBasedOnDomainMiddleware(req, res, (err, result) => { + if (err) { + reject(err) + } else { + resolve(result) + } + }) + }) + ) } - beforeEach(function () { - translations = SandboxedModule.require(MODULE_PATH, { - requires: { - '@overleaf/settings': { - i18n: { - escapeHTMLInVars: false, - subdomainLang: { - www: { lngCode: 'en', url: 'https://www.overleaf.com' }, - fr: { lngCode: 'fr', url: 'https://fr.overleaf.com' }, - da: { lngCode: 'da', url: 'https://da.overleaf.com' }, - }, + beforeEach(async function () { + vi.doMock('@overleaf/settings', () => ({ + default: { + i18n: { + escapeHTMLInVars: false, + subdomainLang: { + www: { lngCode: 'en', url: 'https://www.overleaf.com' }, + fr: { lngCode: 'fr', url: 'https://fr.overleaf.com' }, + da: { lngCode: 'da', url: 'https://da.overleaf.com' }, }, }, }, - }) + })) + + translations = (await import(MODULE_PATH)).default req = { url: '/', @@ -41,8 +48,8 @@ describe('Translations', function () { }) describe('translate', function () { - beforeEach(function (done) { - runMiddlewares(done) + beforeEach(async function () { + await runMiddlewares() }) it('works', function () { @@ -55,8 +62,8 @@ describe('Translations', function () { }) describe('interpolation', function () { - beforeEach(function (done) { - runMiddlewares(done) + beforeEach(async function () { + await runMiddlewares() }) it('works', function () { @@ -94,34 +101,28 @@ describe('Translations', function () { }) describe('setLangBasedOnDomainMiddleware', function () { - it('should set the lang to french if the domain is fr', function (done) { + it('should set the lang to french if the domain is fr', async function () { req.headers.host = 'fr.overleaf.com' - runMiddlewares(() => { - expect(req.lng).to.equal('fr') - done() - }) + await runMiddlewares() + expect(req.lng).to.equal('fr') }) describe('suggestedLanguageSubdomainConfig', function () { - it('should set suggestedLanguageSubdomainConfig if the detected lang is different to subdomain lang', function (done) { + it('should set suggestedLanguageSubdomainConfig if the detected lang is different to subdomain lang', async function () { req.headers['accept-language'] = 'da, en-gb;q=0.8, en;q=0.7' req.headers.host = 'fr.overleaf.com' - runMiddlewares(() => { - expect(res.locals.suggestedLanguageSubdomainConfig).to.exist - expect(res.locals.suggestedLanguageSubdomainConfig.lngCode).to.equal( - 'da' - ) - done() - }) + await runMiddlewares() + expect(res.locals.suggestedLanguageSubdomainConfig).to.exist + expect(res.locals.suggestedLanguageSubdomainConfig.lngCode).to.equal( + 'da' + ) }) - it('should not set suggestedLanguageSubdomainConfig if the detected lang is the same as subdomain lang', function (done) { + it('should not set suggestedLanguageSubdomainConfig if the detected lang is the same as subdomain lang', async function () { req.headers['accept-language'] = 'da, en-gb;q=0.8, en;q=0.7' req.headers.host = 'da.overleaf.com' - runMiddlewares(() => { - expect(res.locals.suggestedLanguageSubdomainConfig).to.not.exist - done() - }) + await runMiddlewares() + expect(res.locals.suggestedLanguageSubdomainConfig).to.not.exist }) }) })