mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-27 11:01:56 +02:00
Merge pull request #28544 from overleaf/ac-some-web-esm-migration-4
[web] Convert some Features files to ES modules (part 4) GitOrigin-RevId: cf11a7584e39c4d4de08e2f924240e488a4066c4
This commit is contained in:
@@ -5,7 +5,7 @@ import EditorRealTimeController from '../Editor/EditorRealTimeController.js'
|
||||
import SessionManager from '../Authentication/SessionManager.js'
|
||||
import UserInfoManager from '../User/UserInfoManager.js'
|
||||
import UserInfoController from '../User/UserInfoController.js'
|
||||
import ChatManager from './ChatManager.js'
|
||||
import ChatManager from './ChatManager.mjs'
|
||||
|
||||
async function sendMessage(req, res) {
|
||||
const { project_id: projectId } = req.params
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const UserInfoController = require('../User/UserInfoController')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const { callbackify } = require('@overleaf/promise-utils')
|
||||
import UserInfoController from '../User/UserInfoController.js'
|
||||
import UserGetter from '../User/UserGetter.js'
|
||||
import { callbackify } from '@overleaf/promise-utils'
|
||||
|
||||
async function injectUserInfoIntoThreads(threads) {
|
||||
const userIds = new Set()
|
||||
@@ -38,7 +38,7 @@ async function injectUserInfoIntoThreads(threads) {
|
||||
return threads
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
injectUserInfoIntoThreads: callbackify(injectUserInfoIntoThreads),
|
||||
promises: {
|
||||
injectUserInfoIntoThreads,
|
||||
@@ -4,7 +4,7 @@ import { Cookie } from 'tough-cookie'
|
||||
import OError from '@overleaf/o-error'
|
||||
import Metrics from '@overleaf/metrics'
|
||||
import ProjectGetter from '../Project/ProjectGetter.js'
|
||||
import CompileManager from './CompileManager.js'
|
||||
import CompileManager from './CompileManager.mjs'
|
||||
import ClsiManager from './ClsiManager.js'
|
||||
import logger from '@overleaf/logger'
|
||||
import Settings from '@overleaf/settings'
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import Crypto from 'node:crypto'
|
||||
import Settings from '@overleaf/settings'
|
||||
import RedisWrapper from '../../infrastructure/RedisWrapper.js'
|
||||
import ProjectGetter from '../Project/ProjectGetter.js'
|
||||
import ProjectRootDocManager from '../Project/ProjectRootDocManager.js'
|
||||
import UserGetter from '../User/UserGetter.js'
|
||||
import ClsiManager from './ClsiManager.js'
|
||||
import Metrics from '@overleaf/metrics'
|
||||
import { RateLimiter } from '../../infrastructure/RateLimiter.js'
|
||||
import UserAnalyticsIdCache from '../Analytics/UserAnalyticsIdCache.js'
|
||||
import { callbackify, callbackifyMultiResult } from '@overleaf/promise-utils'
|
||||
let CompileManager
|
||||
const Crypto = require('crypto')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const RedisWrapper = require('../../infrastructure/RedisWrapper')
|
||||
const rclient = RedisWrapper.client('clsi_recently_compiled')
|
||||
const ProjectGetter = require('../Project/ProjectGetter')
|
||||
const ProjectRootDocManager = require('../Project/ProjectRootDocManager')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const ClsiManager = require('./ClsiManager')
|
||||
const Metrics = require('@overleaf/metrics')
|
||||
const { RateLimiter } = require('../../infrastructure/RateLimiter')
|
||||
const UserAnalyticsIdCache = require('../Analytics/UserAnalyticsIdCache')
|
||||
const {
|
||||
callbackify,
|
||||
callbackifyMultiResult,
|
||||
} = require('@overleaf/promise-utils')
|
||||
|
||||
function instrumentWithTimer(fn, key) {
|
||||
return async (...args) => {
|
||||
@@ -196,7 +193,7 @@ async function deleteAuxFiles(projectId, userId, clsiserverid) {
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = CompileManager = {
|
||||
export default CompileManager = {
|
||||
promises: {
|
||||
compile: instrumentedCompile,
|
||||
deleteAuxFiles,
|
||||
@@ -1,7 +1,7 @@
|
||||
import { isZodErrorLike, fromZodError } from 'zod-validation-error'
|
||||
import Errors from './Errors.js'
|
||||
import SessionManager from '../Authentication/SessionManager.js'
|
||||
import SamlLogHandler from '../SamlLog/SamlLogHandler.js'
|
||||
import SamlLogHandler from '../SamlLog/SamlLogHandler.mjs'
|
||||
import HttpErrorHandler from './HttpErrorHandler.js'
|
||||
import { plainTextResponse } from '../../infrastructure/Response.js'
|
||||
import { expressifyErrorHandler } from '@overleaf/promise-utils'
|
||||
|
||||
@@ -14,7 +14,7 @@ import ChatApiHandler from '../Chat/ChatApiHandler.js'
|
||||
import DocstoreManager from '../Docstore/DocstoreManager.js'
|
||||
import logger from '@overleaf/logger'
|
||||
import EditorRealTimeController from '../Editor/EditorRealTimeController.js'
|
||||
import ChatManager from '../Chat/ChatManager.js'
|
||||
import ChatManager from '../Chat/ChatManager.mjs'
|
||||
import OError from '@overleaf/o-error'
|
||||
import ProjectGetter from '../Project/ProjectGetter.js'
|
||||
import ProjectEntityHandler from '../Project/ProjectEntityHandler.js'
|
||||
|
||||
@@ -15,21 +15,7 @@ import Settings from '@overleaf/settings'
|
||||
import _ from 'lodash'
|
||||
import AnalyticsManager from '../../../../app/src/Features/Analytics/AnalyticsManager.js'
|
||||
import LinkedFilesHandler from './LinkedFilesHandler.mjs'
|
||||
import {
|
||||
CompileFailedError,
|
||||
UrlFetchFailedError,
|
||||
InvalidUrlError,
|
||||
AccessDeniedError,
|
||||
BadEntityTypeError,
|
||||
BadDataError,
|
||||
ProjectNotFoundError,
|
||||
V1ProjectNotFoundError,
|
||||
SourceFileNotFoundError,
|
||||
NotOriginalImporterError,
|
||||
FeatureNotAvailableError,
|
||||
RemoteServiceError,
|
||||
FileCannotRefreshError,
|
||||
} from './LinkedFilesErrors.js'
|
||||
import LinkedFilesErrors from './LinkedFilesErrors.mjs'
|
||||
import {
|
||||
OutputFileFetchFailedError,
|
||||
FileTooLargeError,
|
||||
@@ -45,6 +31,22 @@ import ProjectOutputFileAgent from './ProjectOutputFileAgent.mjs'
|
||||
import ProjectFileAgent from './ProjectFileAgent.mjs'
|
||||
import UrlAgent from './UrlAgent.mjs'
|
||||
|
||||
const {
|
||||
CompileFailedError,
|
||||
UrlFetchFailedError,
|
||||
InvalidUrlError,
|
||||
AccessDeniedError,
|
||||
BadEntityTypeError,
|
||||
BadDataError,
|
||||
ProjectNotFoundError,
|
||||
V1ProjectNotFoundError,
|
||||
SourceFileNotFoundError,
|
||||
NotOriginalImporterError,
|
||||
FeatureNotAvailableError,
|
||||
RemoteServiceError,
|
||||
FileCannotRefreshError,
|
||||
} = LinkedFilesErrors
|
||||
|
||||
let LinkedFilesController
|
||||
|
||||
const createLinkedFileSchema = z.object({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { BackwardCompatibleError } = require('../Errors/Errors')
|
||||
import { BackwardCompatibleError } from '../Errors/Errors.js'
|
||||
|
||||
class UrlFetchFailedError extends BackwardCompatibleError {}
|
||||
|
||||
@@ -26,7 +26,7 @@ class RemoteServiceError extends BackwardCompatibleError {}
|
||||
|
||||
class FileCannotRefreshError extends BackwardCompatibleError {}
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
CompileFailedError,
|
||||
UrlFetchFailedError,
|
||||
InvalidUrlError,
|
||||
@@ -3,13 +3,12 @@ import EditorController from '../Editor/EditorController.js'
|
||||
import ProjectLocator from '../Project/ProjectLocator.js'
|
||||
import { Project } from '../../models/Project.js'
|
||||
import ProjectGetter from '../Project/ProjectGetter.js'
|
||||
import {
|
||||
ProjectNotFoundError,
|
||||
V1ProjectNotFoundError,
|
||||
BadDataError,
|
||||
} from './LinkedFilesErrors.js'
|
||||
import LinkedFilesErrors from './LinkedFilesErrors.mjs'
|
||||
import { callbackifyAll } from '@overleaf/promise-utils'
|
||||
|
||||
const { ProjectNotFoundError, V1ProjectNotFoundError, BadDataError } =
|
||||
LinkedFilesErrors
|
||||
|
||||
const LinkedFilesHandler = {
|
||||
async getFileById(projectId, fileId) {
|
||||
const { element, path, folder } = await ProjectLocator.promises.findElement(
|
||||
|
||||
@@ -16,16 +16,16 @@ import DocstoreManager from '../Docstore/DocstoreManager.js'
|
||||
import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.js'
|
||||
import _ from 'lodash'
|
||||
import LinkedFilesHandler from './LinkedFilesHandler.mjs'
|
||||
import LinkedFilesErrors from './LinkedFilesErrors.mjs'
|
||||
import { promisify } from '@overleaf/promise-utils'
|
||||
import HistoryManager from '../History/HistoryManager.js'
|
||||
|
||||
import {
|
||||
const {
|
||||
BadDataError,
|
||||
AccessDeniedError,
|
||||
BadEntityTypeError,
|
||||
SourceFileNotFoundError,
|
||||
} from './LinkedFilesErrors.js'
|
||||
|
||||
import { promisify } from '@overleaf/promise-utils'
|
||||
import HistoryManager from '../History/HistoryManager.js'
|
||||
} = LinkedFilesErrors
|
||||
|
||||
let ProjectFileAgent
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import AuthorizationManager from '../Authorization/AuthorizationManager.js'
|
||||
import CompileManager from '../Compile/CompileManager.js'
|
||||
import CompileManager from '../Compile/CompileManager.mjs'
|
||||
import ClsiManager from '../Compile/ClsiManager.js'
|
||||
import ProjectFileAgent from './ProjectFileAgent.mjs'
|
||||
import _ from 'lodash'
|
||||
import {
|
||||
CompileFailedError,
|
||||
BadDataError,
|
||||
AccessDeniedError,
|
||||
} from './LinkedFilesErrors.js'
|
||||
import LinkedFilesErrors from './LinkedFilesErrors.mjs'
|
||||
import { OutputFileFetchFailedError } from '../Errors/Errors.js'
|
||||
import LinkedFilesHandler from './LinkedFilesHandler.mjs'
|
||||
import { promisify } from '@overleaf/promise-utils'
|
||||
|
||||
const { CompileFailedError, BadDataError, AccessDeniedError } =
|
||||
LinkedFilesErrors
|
||||
|
||||
function _prepare(projectId, linkedFileData, userId, callback) {
|
||||
_checkAuth(projectId, linkedFileData, userId, (err, allowed) => {
|
||||
if (err) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import logger from '@overleaf/logger'
|
||||
import urlValidator from 'valid-url'
|
||||
import { InvalidUrlError, UrlFetchFailedError } from './LinkedFilesErrors.js'
|
||||
import LinkedFilesErrors from './LinkedFilesErrors.mjs'
|
||||
import LinkedFilesHandler from './LinkedFilesHandler.mjs'
|
||||
import UrlHelper from '../Helpers/UrlHelper.js'
|
||||
import { fetchStream, RequestFailedError } from '@overleaf/fetch-utils'
|
||||
import { callbackify } from '@overleaf/promise-utils'
|
||||
import { FileTooLargeError } from '../Errors/Errors.js'
|
||||
|
||||
const { InvalidUrlError, UrlFetchFailedError } = LinkedFilesErrors
|
||||
|
||||
async function createLinkedFile(
|
||||
projectId,
|
||||
linkedFileData,
|
||||
|
||||
@@ -35,20 +35,20 @@ import AnalyticsManager from '../Analytics/AnalyticsManager.js'
|
||||
import SplitTestHandler from '../SplitTests/SplitTestHandler.js'
|
||||
import SplitTestSessionHandler from '../SplitTests/SplitTestSessionHandler.js'
|
||||
import FeaturesUpdater from '../Subscription/FeaturesUpdater.js'
|
||||
import SpellingHandler from '../Spelling/SpellingHandler.js'
|
||||
import SpellingHandler from '../Spelling/SpellingHandler.mjs'
|
||||
import { hasAdminAccess } from '../Helpers/AdminAuthorizationHelper.js'
|
||||
import InstitutionsFeatures from '../Institutions/InstitutionsFeatures.js'
|
||||
import InstitutionsGetter from '../Institutions/InstitutionsGetter.js'
|
||||
import ProjectAuditLogHandler from './ProjectAuditLogHandler.mjs'
|
||||
import PublicAccessLevels from '../Authorization/PublicAccessLevels.js'
|
||||
import TagsHandler from '../Tags/TagsHandler.js'
|
||||
import TutorialHandler from '../Tutorial/TutorialHandler.js'
|
||||
import TutorialHandler from '../Tutorial/TutorialHandler.mjs'
|
||||
import UserUpdater from '../User/UserUpdater.js'
|
||||
import Modules from '../../infrastructure/Modules.js'
|
||||
import { z, zz, validateReq } from '../../infrastructure/Validation.js'
|
||||
import UserGetter from '../User/UserGetter.js'
|
||||
import { isStandaloneAiAddOnPlanCode } from '../Subscription/AiHelper.js'
|
||||
import SubscriptionController from '../Subscription/SubscriptionController.js'
|
||||
import SubscriptionController from '../Subscription/SubscriptionController.mjs'
|
||||
import { formatCurrency } from '../../util/currency.js'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
@@ -25,7 +25,7 @@ import NotificationsBuilder from '../Notifications/NotificationsBuilder.js'
|
||||
import GeoIpLookup from '../../infrastructure/GeoIpLookup.js'
|
||||
import SplitTestHandler from '../SplitTests/SplitTestHandler.js'
|
||||
import SplitTestSessionHandler from '../SplitTests/SplitTestSessionHandler.js'
|
||||
import TutorialHandler from '../Tutorial/TutorialHandler.js'
|
||||
import TutorialHandler from '../Tutorial/TutorialHandler.mjs'
|
||||
import SubscriptionHelper from '../Subscription/SubscriptionHelper.js'
|
||||
import PermissionsManager from '../Authorization/PermissionsManager.js'
|
||||
import AnalyticsManager from '../Analytics/AnalyticsManager.js'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const { SamlLog } = require('../../models/SamlLog')
|
||||
const SessionManager = require('../Authentication/SessionManager')
|
||||
const logger = require('@overleaf/logger')
|
||||
const { err: errSerializer } = require('@overleaf/logger/serializers')
|
||||
const { callbackify } = require('util')
|
||||
const Settings = require('@overleaf/settings')
|
||||
import { SamlLog } from '../../models/SamlLog.js'
|
||||
import SessionManager from '../Authentication/SessionManager.js'
|
||||
import logger from '@overleaf/logger'
|
||||
import loggerSerializers from '@overleaf/logger/serializers.js'
|
||||
import { callbackify } from 'node:util'
|
||||
import Settings from '@overleaf/settings'
|
||||
|
||||
const ALLOWED_PATHS = Settings.saml?.logAllowList || ['/saml/']
|
||||
|
||||
@@ -33,7 +33,7 @@ async function log(req, data, samlAssertion) {
|
||||
data.samlSession = saml
|
||||
|
||||
if (data.error instanceof Error) {
|
||||
const errSerialized = errSerializer(data.error)
|
||||
const errSerialized = loggerSerializers.err(data.error)
|
||||
if (data.error.tryAgain) {
|
||||
errSerialized.tryAgain = data.error.tryAgain
|
||||
}
|
||||
@@ -82,4 +82,4 @@ const SamlLogHandler = {
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = SamlLogHandler
|
||||
export default SamlLogHandler
|
||||
@@ -5,7 +5,7 @@ import Settings from '@overleaf/settings'
|
||||
import TpdsUpdateSender from '../ThirdPartyDataStore/TpdsUpdateSender.js'
|
||||
import TpdsProjectFlusher from '../ThirdPartyDataStore/TpdsProjectFlusher.js'
|
||||
import EditorRealTimeController from '../Editor/EditorRealTimeController.js'
|
||||
import SystemMessageManager from '../SystemMessages/SystemMessageManager.js'
|
||||
import SystemMessageManager from '../SystemMessages/SystemMessageManager.mjs'
|
||||
|
||||
const AdminController = {
|
||||
_sendDisconnectAllUsersMessage: delay => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const OError = require('@overleaf/o-error')
|
||||
const Metrics = require('@overleaf/metrics')
|
||||
const { promisifyAll } = require('@overleaf/promise-utils')
|
||||
const LearnedWordsManager = require('./LearnedWordsManager')
|
||||
import OError from '@overleaf/o-error'
|
||||
import Metrics from '@overleaf/metrics'
|
||||
import { promisifyAll } from '@overleaf/promise-utils'
|
||||
import LearnedWordsManager from './LearnedWordsManager.js'
|
||||
|
||||
module.exports = {
|
||||
const SpellingHandler = {
|
||||
getUserDictionary(userId, callback) {
|
||||
const timer = new Metrics.Timer('spelling_get_dict')
|
||||
LearnedWordsManager.getLearnedWords(userId, (error, words) => {
|
||||
@@ -26,4 +26,4 @@ module.exports = {
|
||||
},
|
||||
}
|
||||
|
||||
module.exports.promises = promisifyAll(module.exports)
|
||||
export default { ...SpellingHandler, promises: promisifyAll(SpellingHandler) }
|
||||
@@ -1,48 +1,48 @@
|
||||
// @ts-check
|
||||
|
||||
const SessionManager = require('../Authentication/SessionManager')
|
||||
const SubscriptionHandler = require('./SubscriptionHandler')
|
||||
const SubscriptionHelper = require('./SubscriptionHelper')
|
||||
const SubscriptionViewModelBuilder = require('./SubscriptionViewModelBuilder')
|
||||
const LimitationsManager = require('./LimitationsManager')
|
||||
const RecurlyWrapper = require('./RecurlyWrapper')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const logger = require('@overleaf/logger')
|
||||
const GeoIpLookup = require('../../infrastructure/GeoIpLookup')
|
||||
const FeaturesUpdater = require('./FeaturesUpdater')
|
||||
const GroupPlansData = require('./GroupPlansData')
|
||||
const V1SubscriptionManager = require('./V1SubscriptionManager')
|
||||
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
||||
const RecurlyEventHandler = require('./RecurlyEventHandler')
|
||||
const { expressify } = require('@overleaf/promise-utils')
|
||||
const OError = require('@overleaf/o-error')
|
||||
import SessionManager from '../Authentication/SessionManager.js'
|
||||
import SubscriptionHandler from './SubscriptionHandler.js'
|
||||
import SubscriptionHelper from './SubscriptionHelper.js'
|
||||
import SubscriptionViewModelBuilder from './SubscriptionViewModelBuilder.js'
|
||||
import LimitationsManager from './LimitationsManager.js'
|
||||
import RecurlyWrapper from './RecurlyWrapper.js'
|
||||
import Settings from '@overleaf/settings'
|
||||
import logger from '@overleaf/logger'
|
||||
import GeoIpLookup from '../../infrastructure/GeoIpLookup.js'
|
||||
import FeaturesUpdater from './FeaturesUpdater.js'
|
||||
import GroupPlansData from './GroupPlansData.js'
|
||||
import V1SubscriptionManager from './V1SubscriptionManager.js'
|
||||
import AnalyticsManager from '../Analytics/AnalyticsManager.js'
|
||||
import RecurlyEventHandler from './RecurlyEventHandler.js'
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
import OError from '@overleaf/o-error'
|
||||
import Errors from './Errors.js'
|
||||
import SplitTestHandler from '../SplitTests/SplitTestHandler.js'
|
||||
import AuthorizationManager from '../Authorization/AuthorizationManager.js'
|
||||
import Modules from '../../infrastructure/Modules.js'
|
||||
import async from 'async'
|
||||
import HttpErrorHandler from '../Errors/HttpErrorHandler.js'
|
||||
import RecurlyClient from './RecurlyClient.js'
|
||||
import {
|
||||
AI_ADD_ON_CODE,
|
||||
subscriptionChangeIsAiAssistUpgrade,
|
||||
} from './AiHelper.js'
|
||||
import PlansLocator from './PlansLocator.js'
|
||||
import { User } from '../../models/User.js'
|
||||
import UserGetter from '../User/UserGetter.js'
|
||||
import PermissionsManager from '../Authorization/PermissionsManager.js'
|
||||
import { sanitizeSessionUserForFrontEnd } from '../../infrastructure/FrontEndUser.js'
|
||||
import { z, validateReq } from '../../infrastructure/Validation.js'
|
||||
import { IndeterminateInvoiceError } from '../Errors/Errors.js'
|
||||
import SubscriptionLocator from './SubscriptionLocator.js'
|
||||
|
||||
const {
|
||||
DuplicateAddOnError,
|
||||
AddOnNotPresentError,
|
||||
PaymentActionRequiredError,
|
||||
PaymentFailedError,
|
||||
MissingBillingInfoError,
|
||||
} = require('./Errors')
|
||||
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
||||
const AuthorizationManager = require('../Authorization/AuthorizationManager')
|
||||
const Modules = require('../../infrastructure/Modules')
|
||||
const async = require('async')
|
||||
const HttpErrorHandler = require('../Errors/HttpErrorHandler')
|
||||
const RecurlyClient = require('./RecurlyClient')
|
||||
const {
|
||||
AI_ADD_ON_CODE,
|
||||
subscriptionChangeIsAiAssistUpgrade,
|
||||
} = require('./AiHelper')
|
||||
const PlansLocator = require('./PlansLocator')
|
||||
const { User } = require('../../models/User')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const PermissionsManager = require('../Authorization/PermissionsManager')
|
||||
const {
|
||||
sanitizeSessionUserForFrontEnd,
|
||||
} = require('../../infrastructure/FrontEndUser')
|
||||
const { z, validateReq } = require('../../infrastructure/Validation')
|
||||
const { IndeterminateInvoiceError } = require('../Errors/Errors')
|
||||
const SubscriptionLocator = require('./SubscriptionLocator')
|
||||
} = Errors
|
||||
|
||||
const SUBSCRIPTION_PAUSED_REDIRECT_PATH =
|
||||
'/user/subscription?redirect-reason=subscription-paused'
|
||||
@@ -1101,7 +1101,7 @@ function makeChangePreview(
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
userSubscriptionPage: expressify(userSubscriptionPage),
|
||||
successfulSubscription: expressify(successfulSubscription),
|
||||
cancelSubscription,
|
||||
@@ -3,7 +3,7 @@ import _ from 'lodash'
|
||||
import OError from '@overleaf/o-error'
|
||||
import SubscriptionUpdater from './SubscriptionUpdater.js'
|
||||
import SubscriptionLocator from './SubscriptionLocator.js'
|
||||
import SubscriptionController from './SubscriptionController.js'
|
||||
import SubscriptionController from './SubscriptionController.mjs'
|
||||
import SubscriptionHelper from './SubscriptionHelper.js'
|
||||
import { Subscription } from '../../models/Subscription.js'
|
||||
import { User } from '../../models/User.js'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import AuthenticationController from '../Authentication/AuthenticationController.js'
|
||||
import PermissionsController from '../Authorization/PermissionsController.mjs'
|
||||
import SubscriptionController from './SubscriptionController.js'
|
||||
import SubscriptionController from './SubscriptionController.mjs'
|
||||
import SubscriptionGroupController from './SubscriptionGroupController.mjs'
|
||||
import TeamInvitesController from './TeamInvitesController.mjs'
|
||||
import { RateLimiter } from '../../infrastructure/RateLimiter.js'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Settings from '@overleaf/settings'
|
||||
import SessionManager from '../Authentication/SessionManager.js'
|
||||
import SystemMessageManager from './SystemMessageManager.js'
|
||||
import SystemMessageManager from './SystemMessageManager.mjs'
|
||||
|
||||
const ProjectController = {
|
||||
getMessages(req, res, next) {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
const { SystemMessage } = require('../../models/SystemMessage')
|
||||
const {
|
||||
addRequiredCleanupHandlerBeforeDrainingConnections,
|
||||
} = require('../../infrastructure/GracefulShutdown')
|
||||
const { callbackifyAll } = require('@overleaf/promise-utils')
|
||||
const logger = require('@overleaf/logger')
|
||||
import { SystemMessage } from '../../models/SystemMessage.js'
|
||||
import { addRequiredCleanupHandlerBeforeDrainingConnections } from '../../infrastructure/GracefulShutdown.js'
|
||||
import { callbackifyAll } from '@overleaf/promise-utils'
|
||||
import logger from '@overleaf/logger'
|
||||
|
||||
const SystemMessageManager = {
|
||||
_cachedMessages: [],
|
||||
@@ -52,7 +50,7 @@ addRequiredCleanupHandlerBeforeDrainingConnections(
|
||||
}
|
||||
)
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
getMessages: SystemMessageManager.getMessages.bind(SystemMessageManager),
|
||||
...callbackifyAll(SystemMessageManager, { without: ['getMessages'] }),
|
||||
promises: SystemMessageManager,
|
||||
@@ -1,16 +1,16 @@
|
||||
const AuthenticationController = require('../Authentication/AuthenticationController')
|
||||
const TemplatesController = require('./TemplatesController')
|
||||
const TemplatesMiddleware = require('./TemplatesMiddleware')
|
||||
const { RateLimiter } = require('../../infrastructure/RateLimiter')
|
||||
const RateLimiterMiddleware = require('../Security/RateLimiterMiddleware')
|
||||
const AnalyticsRegistrationSourceMiddleware = require('../Analytics/AnalyticsRegistrationSourceMiddleware')
|
||||
import AuthenticationController from '../Authentication/AuthenticationController.js'
|
||||
import TemplatesController from './TemplatesController.js'
|
||||
import TemplatesMiddleware from './TemplatesMiddleware.js'
|
||||
import { RateLimiter } from '../../infrastructure/RateLimiter.js'
|
||||
import RateLimiterMiddleware from '../Security/RateLimiterMiddleware.js'
|
||||
import AnalyticsRegistrationSourceMiddleware from '../Analytics/AnalyticsRegistrationSourceMiddleware.js'
|
||||
|
||||
const rateLimiter = new RateLimiter('create-project-from-template', {
|
||||
points: 20,
|
||||
duration: 60,
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
rateLimiter,
|
||||
apply(app) {
|
||||
app.get(
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
import TpdsUpdateHandler from './TpdsUpdateHandler.mjs'
|
||||
import UpdateMerger from './UpdateMerger.js'
|
||||
import UpdateMerger from './UpdateMerger.mjs'
|
||||
import Errors from '../Errors/Errors.js'
|
||||
import logger from '@overleaf/logger'
|
||||
import Path from 'node:path'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { callbackify } from 'node:util'
|
||||
import UpdateMerger from './UpdateMerger.js'
|
||||
import UpdateMerger from './UpdateMerger.mjs'
|
||||
import logger from '@overleaf/logger'
|
||||
import NotificationsBuilder from '../Notifications/NotificationsBuilder.js'
|
||||
import ProjectCreationHandler from '../Project/ProjectCreationHandler.js'
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
const { callbackify } = require('util')
|
||||
const _ = require('lodash')
|
||||
const fsPromises = require('fs/promises')
|
||||
const fs = require('fs')
|
||||
const logger = require('@overleaf/logger')
|
||||
const EditorController = require('../Editor/EditorController')
|
||||
const FileTypeManager = require('../Uploads/FileTypeManager')
|
||||
const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
|
||||
const crypto = require('crypto')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const { pipeline } = require('stream/promises')
|
||||
import { callbackify } from 'node:util'
|
||||
import _ from 'lodash'
|
||||
import fsPromises from 'node:fs/promises'
|
||||
import fs from 'node:fs'
|
||||
import logger from '@overleaf/logger'
|
||||
import EditorController from '../Editor/EditorController.js'
|
||||
import FileTypeManager from '../Uploads/FileTypeManager.js'
|
||||
import ProjectEntityHandler from '../Project/ProjectEntityHandler.js'
|
||||
import crypto from 'node:crypto'
|
||||
import Settings from '@overleaf/settings'
|
||||
import { pipeline } from 'node:stream/promises'
|
||||
|
||||
async function mergeUpdate(userId, projectId, path, updateRequest, source) {
|
||||
const fsPath = await writeUpdateToDisk(projectId, updateRequest)
|
||||
@@ -185,7 +185,7 @@ async function createFolder(projectId, path, userId) {
|
||||
return folder
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
mergeUpdate: callbackify(mergeUpdate),
|
||||
_mergeUpdate: callbackify(_mergeUpdate),
|
||||
deleteUpdate: callbackify(deleteUpdate),
|
||||
@@ -1,5 +1,5 @@
|
||||
import SessionManager from '../Authentication/SessionManager.js'
|
||||
import TutorialHandler from './TutorialHandler.js'
|
||||
import TutorialHandler from './TutorialHandler.mjs'
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
|
||||
const VALID_KEYS = [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const UserUpdater = require('../User/UserUpdater')
|
||||
import UserUpdater from '../User/UserUpdater.js'
|
||||
|
||||
const POSTPONE_DURATION_MS = 24 * 60 * 60 * 1000 // 1 day
|
||||
|
||||
@@ -59,4 +59,4 @@ function getInactiveTutorials(user, tutorialKey) {
|
||||
return inactiveTutorials
|
||||
}
|
||||
|
||||
module.exports = { setTutorialState, getInactiveTutorials }
|
||||
export default { setTutorialState, getInactiveTutorials }
|
||||
@@ -24,7 +24,7 @@ import UserEmailsController from './Features/User/UserEmailsController.js'
|
||||
import UserPagesController from './Features/User/UserPagesController.mjs'
|
||||
import TutorialController from './Features/Tutorial/TutorialController.mjs'
|
||||
import DocumentController from './Features/Documents/DocumentController.mjs'
|
||||
import CompileManager from './Features/Compile/CompileManager.js'
|
||||
import CompileManager from './Features/Compile/CompileManager.mjs'
|
||||
import CompileController from './Features/Compile/CompileController.mjs'
|
||||
import HealthCheckController from './Features/HealthCheck/HealthCheckController.mjs'
|
||||
import ProjectDownloadsController from './Features/Downloads/ProjectDownloadsController.mjs'
|
||||
@@ -52,7 +52,7 @@ import MetaController from './Features/Metadata/MetaController.mjs'
|
||||
import TokenAccessController from './Features/TokenAccess/TokenAccessController.mjs'
|
||||
import TokenAccessRouter from './Features/TokenAccess/TokenAccessRouter.mjs'
|
||||
import LinkedFilesRouter from './Features/LinkedFiles/LinkedFilesRouter.mjs'
|
||||
import TemplatesRouter from './Features/Templates/TemplatesRouter.js'
|
||||
import TemplatesRouter from './Features/Templates/TemplatesRouter.mjs'
|
||||
import UserMembershipRouter from './Features/UserMembership/UserMembershipRouter.mjs'
|
||||
import SystemMessageController from './Features/SystemMessages/SystemMessageController.mjs'
|
||||
import AnalyticsRegistrationSourceMiddleware from './Features/Analytics/AnalyticsRegistrationSourceMiddleware.js'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AbstractMockApi from './AbstractMockApi.mjs'
|
||||
import SubscriptionController from '../../../../app/src/Features/Subscription/SubscriptionController.js'
|
||||
import SubscriptionController from '../../../../app/src/Features/Subscription/SubscriptionController.mjs'
|
||||
import { xmlResponse } from '../../../../app/src/infrastructure/Response.js'
|
||||
|
||||
class MockRecurlyApi extends AbstractMockApi {
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('ChatController', function () {
|
||||
default: ctx.ChatApiHandler,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Chat/ChatManager.js', () => ({
|
||||
vi.doMock('../../../../app/src/Features/Chat/ChatManager.mjs', () => ({
|
||||
default: ctx.ChatManager,
|
||||
}))
|
||||
|
||||
|
||||
129
services/web/test/unit/src/Chat/ChatManager.test.mjs
Normal file
129
services/web/test/unit/src/Chat/ChatManager.test.mjs
Normal file
@@ -0,0 +1,129 @@
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const modulePath = '../../../../app/src/Features/Chat/ChatManager.mjs'
|
||||
|
||||
describe('ChatManager', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.user_id = 'mock-user-id'
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: (ctx.UserGetter = { promises: {} }),
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserInfoController', () => ({
|
||||
default: (ctx.UserInfoController = {}),
|
||||
}))
|
||||
|
||||
ctx.ChatManager = (await import(modulePath)).default
|
||||
ctx.req = {
|
||||
params: {
|
||||
project_id: ctx.project_id,
|
||||
},
|
||||
}
|
||||
ctx.res = {
|
||||
json: sinon.stub(),
|
||||
send: sinon.stub(),
|
||||
sendStatus: sinon.stub(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('injectUserInfoIntoThreads', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.users = {
|
||||
user_id_1: {
|
||||
_id: 'user_id_1',
|
||||
},
|
||||
user_id_2: {
|
||||
_id: 'user_id_2',
|
||||
},
|
||||
}
|
||||
ctx.UserGetter.promises.getUsers = userIds =>
|
||||
Promise.resolve(
|
||||
Array.from(userIds)
|
||||
.map(id => ctx.users[id])
|
||||
.filter(u => !!u)
|
||||
)
|
||||
|
||||
sinon.spy(ctx.UserGetter.promises, 'getUsers')
|
||||
ctx.UserInfoController.formatPersonalInfo = user => ({
|
||||
formatted: { id: user._id.toString() },
|
||||
})
|
||||
})
|
||||
|
||||
it('should inject a user object into messaged and resolved data', async function (ctx) {
|
||||
const threads = await ctx.ChatManager.promises.injectUserInfoIntoThreads({
|
||||
thread1: {
|
||||
resolved: true,
|
||||
resolved_by_user_id: 'user_id_1',
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
content: 'foo',
|
||||
},
|
||||
{
|
||||
user_id: 'user_id_2',
|
||||
content: 'bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
thread2: {
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
content: 'baz',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(threads).to.deep.equal({
|
||||
thread1: {
|
||||
resolved: true,
|
||||
resolved_by_user_id: 'user_id_1',
|
||||
resolved_by_user: { formatted: { id: 'user_id_1' } },
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
user: { formatted: { id: 'user_id_1' } },
|
||||
content: 'foo',
|
||||
},
|
||||
{
|
||||
user_id: 'user_id_2',
|
||||
user: { formatted: { id: 'user_id_2' } },
|
||||
content: 'bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
thread2: {
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
user: { formatted: { id: 'user_id_1' } },
|
||||
content: 'baz',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should lookup all users in a single batch', async function (ctx) {
|
||||
await ctx.ChatManager.promises.injectUserInfoIntoThreads([
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
content: 'foo',
|
||||
},
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
content: 'bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
ctx.UserGetter.promises.getUsers.should.have.been.calledOnce
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,131 +0,0 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const path = require('path')
|
||||
const sinon = require('sinon')
|
||||
const modulePath = path.join(
|
||||
__dirname,
|
||||
'../../../../app/src/Features/Chat/ChatManager'
|
||||
)
|
||||
const { expect } = require('chai')
|
||||
|
||||
describe('ChatManager', function () {
|
||||
beforeEach(function () {
|
||||
this.user_id = 'mock-user-id'
|
||||
this.ChatManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../User/UserGetter': (this.UserGetter = { promises: {} }),
|
||||
'../User/UserInfoController': (this.UserInfoController = {}),
|
||||
},
|
||||
})
|
||||
this.req = {
|
||||
params: {
|
||||
project_id: this.project_id,
|
||||
},
|
||||
}
|
||||
this.res = {
|
||||
json: sinon.stub(),
|
||||
send: sinon.stub(),
|
||||
sendStatus: sinon.stub(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('injectUserInfoIntoThreads', function () {
|
||||
beforeEach(function () {
|
||||
this.users = {
|
||||
user_id_1: {
|
||||
_id: 'user_id_1',
|
||||
},
|
||||
user_id_2: {
|
||||
_id: 'user_id_2',
|
||||
},
|
||||
}
|
||||
this.UserGetter.promises.getUsers = userIds =>
|
||||
Promise.resolve(
|
||||
Array.from(userIds)
|
||||
.map(id => this.users[id])
|
||||
.filter(u => !!u)
|
||||
)
|
||||
|
||||
sinon.spy(this.UserGetter.promises, 'getUsers')
|
||||
return (this.UserInfoController.formatPersonalInfo = user => ({
|
||||
formatted: { id: user._id.toString() },
|
||||
}))
|
||||
})
|
||||
|
||||
it('should inject a user object into messaged and resolved data', async function () {
|
||||
const threads = await this.ChatManager.promises.injectUserInfoIntoThreads(
|
||||
{
|
||||
thread1: {
|
||||
resolved: true,
|
||||
resolved_by_user_id: 'user_id_1',
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
content: 'foo',
|
||||
},
|
||||
{
|
||||
user_id: 'user_id_2',
|
||||
content: 'bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
thread2: {
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
content: 'baz',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(threads).to.deep.equal({
|
||||
thread1: {
|
||||
resolved: true,
|
||||
resolved_by_user_id: 'user_id_1',
|
||||
resolved_by_user: { formatted: { id: 'user_id_1' } },
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
user: { formatted: { id: 'user_id_1' } },
|
||||
content: 'foo',
|
||||
},
|
||||
{
|
||||
user_id: 'user_id_2',
|
||||
user: { formatted: { id: 'user_id_2' } },
|
||||
content: 'bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
thread2: {
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
user: { formatted: { id: 'user_id_1' } },
|
||||
content: 'baz',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should lookup all users in a single batch', async function () {
|
||||
await this.ChatManager.promises.injectUserInfoIntoThreads([
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
content: 'foo',
|
||||
},
|
||||
{
|
||||
user_id: 'user_id_1',
|
||||
content: 'bar',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
this.UserGetter.promises.getUsers.should.have.been.calledOnce
|
||||
})
|
||||
})
|
||||
})
|
||||
457
services/web/test/unit/src/Compile/CompileManager.test.mjs
Normal file
457
services/web/test/unit/src/Compile/CompileManager.test.mjs
Normal file
@@ -0,0 +1,457 @@
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const MODULE_PATH = '../../../../app/src/Features/Compile/CompileManager.mjs'
|
||||
|
||||
describe('CompileManager', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.rateLimiter = {
|
||||
consume: sinon.stub().resolves(),
|
||||
}
|
||||
ctx.timer = {
|
||||
done: sinon.stub(),
|
||||
}
|
||||
ctx.Metrics = {
|
||||
Timer: sinon.stub().returns(ctx.timer),
|
||||
inc: sinon.stub(),
|
||||
}
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: (ctx.settings = {
|
||||
apis: {
|
||||
clsi: { submissionBackendClass: 'n2d' },
|
||||
},
|
||||
redis: { web: { host: '127.0.0.1', port: 42 } },
|
||||
rateLimit: { autoCompile: {} },
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/RedisWrapper', () => ({
|
||||
default: {
|
||||
client: () =>
|
||||
(ctx.rclient = {
|
||||
auth() {},
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectRootDocManager',
|
||||
() => ({
|
||||
default: (ctx.ProjectRootDocManager = {
|
||||
promises: {},
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
|
||||
default: (ctx.ProjectGetter = { promises: {} }),
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
|
||||
default: (ctx.UserGetter = { promises: {} }),
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Compile/ClsiManager', () => ({
|
||||
default: (ctx.ClsiManager = { promises: {} }),
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/RateLimiter.js', () => ({
|
||||
RateLimiter: sinon.stub().returns(ctx.rateLimiter),
|
||||
}))
|
||||
|
||||
vi.doMock('@overleaf/metrics', () => ({
|
||||
default: ctx.Metrics,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Analytics/UserAnalyticsIdCache',
|
||||
() => ({
|
||||
default: (ctx.UserAnalyticsIdCache = {
|
||||
get: sinon.stub().resolves('abc'),
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/SplitTests/SplitTestHandler',
|
||||
() => ({
|
||||
default: (ctx.SplitTestHandler = {
|
||||
promises: {},
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
ctx.CompileManager = (await import(MODULE_PATH)).default
|
||||
ctx.project_id = 'mock-project-id-123'
|
||||
ctx.user_id = 'mock-user-id-123'
|
||||
ctx.callback = sinon.stub()
|
||||
ctx.limits = {
|
||||
timeout: 42,
|
||||
compileGroup: 'standard',
|
||||
}
|
||||
})
|
||||
|
||||
describe('compile', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.CompileManager._checkIfRecentlyCompiled = sinon.stub().resolves(false)
|
||||
ctx.ProjectRootDocManager.promises.ensureRootDocumentIsSet = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
ctx.CompileManager.promises.getProjectCompileLimits = sinon
|
||||
.stub()
|
||||
.resolves(ctx.limits)
|
||||
ctx.ClsiManager.promises.sendRequest = sinon.stub().resolves({
|
||||
status: (ctx.status = 'mock-status'),
|
||||
outputFiles: (ctx.outputFiles = []),
|
||||
clsiServerId: (ctx.output = 'mock output'),
|
||||
})
|
||||
})
|
||||
|
||||
describe('succesfully', function () {
|
||||
let result
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
|
||||
isAutoCompile,
|
||||
compileGroup
|
||||
) => true
|
||||
ctx.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.resolves(
|
||||
(ctx.project = { owner_ref: (ctx.owner_id = 'owner-id-123') })
|
||||
)
|
||||
ctx.UserGetter.promises.getUser = sinon.stub().resolves(
|
||||
(ctx.user = {
|
||||
features: { compileTimeout: '20s', compileGroup: 'standard' },
|
||||
analyticsId: 'abc',
|
||||
})
|
||||
)
|
||||
result = await ctx.CompileManager.promises.compile(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
it('should check the project has not been recently compiled', function (ctx) {
|
||||
ctx.CompileManager._checkIfRecentlyCompiled
|
||||
.calledWith(ctx.project_id, ctx.user_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should ensure that the root document is set', function (ctx) {
|
||||
ctx.ProjectRootDocManager.promises.ensureRootDocumentIsSet
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should get the project compile limits', function (ctx) {
|
||||
ctx.CompileManager.promises.getProjectCompileLimits
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should run the compile with the compile limits', function (ctx) {
|
||||
ctx.ClsiManager.promises.sendRequest
|
||||
.calledWith(ctx.project_id, ctx.user_id, {
|
||||
timeout: ctx.limits.timeout,
|
||||
compileGroup: 'standard',
|
||||
buildId: sinon.match(/[a-f0-9]+-[a-f0-9]+/),
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve with the output', function (ctx) {
|
||||
expect(result).to.haveOwnProperty('status', ctx.status)
|
||||
expect(result).to.haveOwnProperty('clsiServerId', ctx.output)
|
||||
expect(result).to.haveOwnProperty('outputFiles', ctx.outputFiles)
|
||||
})
|
||||
|
||||
it('should time the compile', function (ctx) {
|
||||
ctx.timer.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has been recently compiled', function () {
|
||||
it('should return', async function (ctx) {
|
||||
ctx.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
|
||||
isAutoCompile,
|
||||
compileGroup
|
||||
) => true
|
||||
ctx.CompileManager._checkIfRecentlyCompiled = sinon
|
||||
.stub()
|
||||
.resolves(true)
|
||||
const { status } = await ctx.CompileManager.promises.compile(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
{}
|
||||
)
|
||||
status.should.equal('too-recently-compiled')
|
||||
})
|
||||
})
|
||||
|
||||
describe('should check the rate limit', function () {
|
||||
it('should return', async function (ctx) {
|
||||
ctx.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
|
||||
.stub()
|
||||
.resolves(false)
|
||||
const { status } = await ctx.CompileManager.promises.compile(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
{}
|
||||
)
|
||||
|
||||
expect(status).to.equal('autocompile-backoff')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getProjectCompileLimits', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.features = {
|
||||
compileTimeout: (ctx.timeout = 42),
|
||||
compileGroup: (ctx.group = 'priority'),
|
||||
}
|
||||
ctx.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.resolves(
|
||||
(ctx.project = { owner_ref: (ctx.owner_id = 'owner-id-123') })
|
||||
)
|
||||
ctx.UserGetter.promises.getUser = sinon
|
||||
.stub()
|
||||
.resolves((ctx.user = { features: ctx.features, analyticsId: 'abc' }))
|
||||
try {
|
||||
const result =
|
||||
await ctx.CompileManager.promises.getProjectCompileLimits(
|
||||
ctx.project_id
|
||||
)
|
||||
ctx.callback(null, result)
|
||||
} catch (error) {
|
||||
ctx.callback(error)
|
||||
}
|
||||
})
|
||||
|
||||
it('should look up the owner of the project', function (ctx) {
|
||||
ctx.ProjectGetter.promises.getProject
|
||||
.calledWith(ctx.project_id, { owner_ref: 1 })
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should look up the owner's features", function (ctx) {
|
||||
ctx.UserGetter.promises.getUser
|
||||
.calledWith(ctx.project.owner_ref, {
|
||||
_id: 1,
|
||||
alphaProgram: 1,
|
||||
analyticsId: 1,
|
||||
betaProgram: 1,
|
||||
features: 1,
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should return the limits', function (ctx) {
|
||||
ctx.callback
|
||||
.calledWith(null, {
|
||||
timeout: ctx.timeout,
|
||||
compileGroup: ctx.group,
|
||||
compileBackendClass: 'c2d',
|
||||
ownerAnalyticsId: 'abc',
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('compileBackendClass', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.features = {
|
||||
compileTimeout: 42,
|
||||
compileGroup: 'standard',
|
||||
}
|
||||
ctx.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.resolves({ owner_ref: 'owner-id-123' })
|
||||
ctx.UserGetter.promises.getUser = sinon
|
||||
.stub()
|
||||
.resolves({ features: ctx.features, analyticsId: 'abc' })
|
||||
})
|
||||
|
||||
describe('with priority compile', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.features.compileGroup = 'priority'
|
||||
})
|
||||
it('should return the default class', async function (ctx) {
|
||||
const { compileBackendClass } =
|
||||
await ctx.CompileManager.promises.getProjectCompileLimits(
|
||||
ctx.project_id
|
||||
)
|
||||
expect(compileBackendClass).to.equal('c2d')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteAuxFiles', function () {
|
||||
let result
|
||||
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.CompileManager.promises.getProjectCompileLimits = sinon
|
||||
.stub()
|
||||
.resolves((ctx.limits = { compileGroup: 'mock-compile-group' }))
|
||||
ctx.ClsiManager.promises.deleteAuxFiles = sinon.stub().resolves('test')
|
||||
result = await ctx.CompileManager.promises.deleteAuxFiles(
|
||||
ctx.project_id,
|
||||
ctx.user_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should look up the compile group to use', function (ctx) {
|
||||
ctx.CompileManager.promises.getProjectCompileLimits
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should delete the aux files', function (ctx) {
|
||||
ctx.ClsiManager.promises.deleteAuxFiles
|
||||
.calledWith(ctx.project_id, ctx.user_id, ctx.limits)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve', function () {
|
||||
expect(result).not.to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
describe('_checkIfRecentlyCompiled', function () {
|
||||
describe('when the key exists in redis', function () {
|
||||
let result
|
||||
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.rclient.set = sinon.stub().resolves(null)
|
||||
result = await ctx.CompileManager._checkIfRecentlyCompiled(
|
||||
ctx.project_id,
|
||||
ctx.user_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should try to set the key', function (ctx) {
|
||||
ctx.rclient.set
|
||||
.calledWith(
|
||||
`compile:${ctx.project_id}:${ctx.user_id}`,
|
||||
true,
|
||||
'EX',
|
||||
ctx.CompileManager.COMPILE_DELAY,
|
||||
'NX'
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve with true', function () {
|
||||
result.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the key does not exist in redis', function () {
|
||||
let result
|
||||
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.rclient.set = sinon.stub().resolves('OK')
|
||||
result = await ctx.CompileManager._checkIfRecentlyCompiled(
|
||||
ctx.project_id,
|
||||
ctx.user_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should try to set the key', function (ctx) {
|
||||
ctx.rclient.set
|
||||
.calledWith(
|
||||
`compile:${ctx.project_id}:${ctx.user_id}`,
|
||||
true,
|
||||
'EX',
|
||||
ctx.CompileManager.COMPILE_DELAY,
|
||||
'NX'
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve with false', function () {
|
||||
result.should.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_checkIfAutoCompileLimitHasBeenHit', function () {
|
||||
it('should be able to compile if it is not an autocompile', async function (ctx) {
|
||||
const canCompile =
|
||||
await ctx.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
false,
|
||||
'everyone'
|
||||
)
|
||||
expect(canCompile).to.equal(true)
|
||||
})
|
||||
|
||||
it('should be able to compile if rate limit has remaining', async function (ctx) {
|
||||
const canCompile =
|
||||
await ctx.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone'
|
||||
)
|
||||
|
||||
expect(ctx.rateLimiter.consume).to.have.been.calledWith('global')
|
||||
expect(canCompile).to.equal(true)
|
||||
})
|
||||
|
||||
it('should be not able to compile if rate limit has no remianing', async function (ctx) {
|
||||
ctx.rateLimiter.consume.rejects({ remainingPoints: 0 })
|
||||
const canCompile =
|
||||
await ctx.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone'
|
||||
)
|
||||
|
||||
expect(canCompile).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return false if there is an error in the rate limit', async function (ctx) {
|
||||
ctx.rateLimiter.consume.rejects(new Error('BOOM!'))
|
||||
const canCompile =
|
||||
await ctx.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone'
|
||||
)
|
||||
|
||||
expect(canCompile).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('wordCount', function () {
|
||||
let result
|
||||
const wordCount = 1
|
||||
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.CompileManager.promises.getProjectCompileLimits = sinon
|
||||
.stub()
|
||||
.resolves((ctx.limits = { compileGroup: 'mock-compile-group' }))
|
||||
ctx.ClsiManager.promises.wordCount = sinon.stub().resolves(wordCount)
|
||||
result = await ctx.CompileManager.promises.wordCount(
|
||||
ctx.project_id,
|
||||
ctx.user_id,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should look up the compile group to use', function (ctx) {
|
||||
ctx.CompileManager.promises.getProjectCompileLimits
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call wordCount for project', function (ctx) {
|
||||
ctx.ClsiManager.promises.wordCount
|
||||
.calledWith(ctx.project_id, ctx.user_id, false, ctx.limits)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve with the wordCount from the ClsiManager', function () {
|
||||
expect(result).to.equal(wordCount)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,426 +0,0 @@
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
const MODULE_PATH = '../../../../app/src/Features/Compile/CompileManager.js'
|
||||
|
||||
describe('CompileManager', function () {
|
||||
beforeEach(function () {
|
||||
this.rateLimiter = {
|
||||
consume: sinon.stub().resolves(),
|
||||
}
|
||||
this.RateLimiter = {
|
||||
RateLimiter: sinon.stub().returns(this.rateLimiter),
|
||||
}
|
||||
this.timer = {
|
||||
done: sinon.stub(),
|
||||
}
|
||||
this.Metrics = {
|
||||
Timer: sinon.stub().returns(this.timer),
|
||||
inc: sinon.stub(),
|
||||
}
|
||||
this.CompileManager = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'@overleaf/settings': (this.settings = {
|
||||
apis: {
|
||||
clsi: { submissionBackendClass: 'n2d' },
|
||||
},
|
||||
redis: { web: { host: '127.0.0.1', port: 42 } },
|
||||
rateLimit: { autoCompile: {} },
|
||||
}),
|
||||
'../../infrastructure/RedisWrapper': {
|
||||
client: () =>
|
||||
(this.rclient = {
|
||||
auth() {},
|
||||
}),
|
||||
},
|
||||
'../Project/ProjectRootDocManager': (this.ProjectRootDocManager = {
|
||||
promises: {},
|
||||
}),
|
||||
'../Project/ProjectGetter': (this.ProjectGetter = { promises: {} }),
|
||||
'../User/UserGetter': (this.UserGetter = { promises: {} }),
|
||||
'./ClsiManager': (this.ClsiManager = { promises: {} }),
|
||||
'../../infrastructure/RateLimiter': this.RateLimiter,
|
||||
'@overleaf/metrics': this.Metrics,
|
||||
'../Analytics/UserAnalyticsIdCache': (this.UserAnalyticsIdCache = {
|
||||
get: sinon.stub().resolves('abc'),
|
||||
}),
|
||||
'../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
|
||||
promises: {},
|
||||
}),
|
||||
},
|
||||
})
|
||||
this.project_id = 'mock-project-id-123'
|
||||
this.user_id = 'mock-user-id-123'
|
||||
this.callback = sinon.stub()
|
||||
this.limits = {
|
||||
timeout: 42,
|
||||
compileGroup: 'standard',
|
||||
}
|
||||
})
|
||||
|
||||
describe('compile', function () {
|
||||
beforeEach(function () {
|
||||
this.CompileManager._checkIfRecentlyCompiled = sinon
|
||||
.stub()
|
||||
.resolves(false)
|
||||
this.ProjectRootDocManager.promises.ensureRootDocumentIsSet = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
this.CompileManager.promises.getProjectCompileLimits = sinon
|
||||
.stub()
|
||||
.resolves(this.limits)
|
||||
this.ClsiManager.promises.sendRequest = sinon.stub().resolves({
|
||||
status: (this.status = 'mock-status'),
|
||||
outputFiles: (this.outputFiles = []),
|
||||
clsiServerId: (this.output = 'mock output'),
|
||||
})
|
||||
})
|
||||
|
||||
describe('succesfully', function () {
|
||||
let result
|
||||
beforeEach(async function () {
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
|
||||
isAutoCompile,
|
||||
compileGroup
|
||||
) => true
|
||||
this.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.resolves(
|
||||
(this.project = { owner_ref: (this.owner_id = 'owner-id-123') })
|
||||
)
|
||||
this.UserGetter.promises.getUser = sinon.stub().resolves(
|
||||
(this.user = {
|
||||
features: { compileTimeout: '20s', compileGroup: 'standard' },
|
||||
analyticsId: 'abc',
|
||||
})
|
||||
)
|
||||
result = await this.CompileManager.promises.compile(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
it('should check the project has not been recently compiled', function () {
|
||||
this.CompileManager._checkIfRecentlyCompiled
|
||||
.calledWith(this.project_id, this.user_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should ensure that the root document is set', function () {
|
||||
this.ProjectRootDocManager.promises.ensureRootDocumentIsSet
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should get the project compile limits', function () {
|
||||
this.CompileManager.promises.getProjectCompileLimits
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should run the compile with the compile limits', function () {
|
||||
this.ClsiManager.promises.sendRequest
|
||||
.calledWith(this.project_id, this.user_id, {
|
||||
timeout: this.limits.timeout,
|
||||
compileGroup: 'standard',
|
||||
buildId: sinon.match(/[a-f0-9]+-[a-f0-9]+/),
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve with the output', function () {
|
||||
expect(result).to.haveOwnProperty('status', this.status)
|
||||
expect(result).to.haveOwnProperty('clsiServerId', this.output)
|
||||
expect(result).to.haveOwnProperty('outputFiles', this.outputFiles)
|
||||
})
|
||||
|
||||
it('should time the compile', function () {
|
||||
this.timer.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has been recently compiled', function () {
|
||||
it('should return', async function () {
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
|
||||
isAutoCompile,
|
||||
compileGroup
|
||||
) => true
|
||||
this.CompileManager._checkIfRecentlyCompiled = sinon
|
||||
.stub()
|
||||
.resolves(true)
|
||||
const { status } = await this.CompileManager.promises.compile(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
{}
|
||||
)
|
||||
status.should.equal('too-recently-compiled')
|
||||
})
|
||||
})
|
||||
|
||||
describe('should check the rate limit', function () {
|
||||
it('should return', async function () {
|
||||
this.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
|
||||
.stub()
|
||||
.resolves(false)
|
||||
const { status } = await this.CompileManager.promises.compile(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
{}
|
||||
)
|
||||
|
||||
expect(status).to.equal('autocompile-backoff')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getProjectCompileLimits', function () {
|
||||
beforeEach(async function () {
|
||||
this.features = {
|
||||
compileTimeout: (this.timeout = 42),
|
||||
compileGroup: (this.group = 'priority'),
|
||||
}
|
||||
this.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.resolves(
|
||||
(this.project = { owner_ref: (this.owner_id = 'owner-id-123') })
|
||||
)
|
||||
this.UserGetter.promises.getUser = sinon
|
||||
.stub()
|
||||
.resolves((this.user = { features: this.features, analyticsId: 'abc' }))
|
||||
try {
|
||||
const result =
|
||||
await this.CompileManager.promises.getProjectCompileLimits(
|
||||
this.project_id
|
||||
)
|
||||
this.callback(null, result)
|
||||
} catch (error) {
|
||||
this.callback(error)
|
||||
}
|
||||
})
|
||||
|
||||
it('should look up the owner of the project', function () {
|
||||
this.ProjectGetter.promises.getProject
|
||||
.calledWith(this.project_id, { owner_ref: 1 })
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should look up the owner's features", function () {
|
||||
this.UserGetter.promises.getUser
|
||||
.calledWith(this.project.owner_ref, {
|
||||
_id: 1,
|
||||
alphaProgram: 1,
|
||||
analyticsId: 1,
|
||||
betaProgram: 1,
|
||||
features: 1,
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should return the limits', function () {
|
||||
this.callback
|
||||
.calledWith(null, {
|
||||
timeout: this.timeout,
|
||||
compileGroup: this.group,
|
||||
compileBackendClass: 'c2d',
|
||||
ownerAnalyticsId: 'abc',
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('compileBackendClass', function () {
|
||||
beforeEach(function () {
|
||||
this.features = {
|
||||
compileTimeout: 42,
|
||||
compileGroup: 'standard',
|
||||
}
|
||||
this.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.resolves({ owner_ref: 'owner-id-123' })
|
||||
this.UserGetter.promises.getUser = sinon
|
||||
.stub()
|
||||
.resolves({ features: this.features, analyticsId: 'abc' })
|
||||
})
|
||||
|
||||
describe('with priority compile', function () {
|
||||
beforeEach(function () {
|
||||
this.features.compileGroup = 'priority'
|
||||
})
|
||||
it('should return the default class', async function () {
|
||||
const { compileBackendClass } =
|
||||
await this.CompileManager.promises.getProjectCompileLimits(
|
||||
this.project_id
|
||||
)
|
||||
expect(compileBackendClass).to.equal('c2d')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteAuxFiles', function () {
|
||||
let result
|
||||
|
||||
beforeEach(async function () {
|
||||
this.CompileManager.promises.getProjectCompileLimits = sinon
|
||||
.stub()
|
||||
.resolves((this.limits = { compileGroup: 'mock-compile-group' }))
|
||||
this.ClsiManager.promises.deleteAuxFiles = sinon.stub().resolves('test')
|
||||
result = await this.CompileManager.promises.deleteAuxFiles(
|
||||
this.project_id,
|
||||
this.user_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should look up the compile group to use', function () {
|
||||
this.CompileManager.promises.getProjectCompileLimits
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should delete the aux files', function () {
|
||||
this.ClsiManager.promises.deleteAuxFiles
|
||||
.calledWith(this.project_id, this.user_id, this.limits)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve', function () {
|
||||
expect(result).not.to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
describe('_checkIfRecentlyCompiled', function () {
|
||||
describe('when the key exists in redis', function () {
|
||||
let result
|
||||
|
||||
beforeEach(async function () {
|
||||
this.rclient.set = sinon.stub().resolves(null)
|
||||
result = await this.CompileManager._checkIfRecentlyCompiled(
|
||||
this.project_id,
|
||||
this.user_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should try to set the key', function () {
|
||||
this.rclient.set
|
||||
.calledWith(
|
||||
`compile:${this.project_id}:${this.user_id}`,
|
||||
true,
|
||||
'EX',
|
||||
this.CompileManager.COMPILE_DELAY,
|
||||
'NX'
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve with true', function () {
|
||||
result.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the key does not exist in redis', function () {
|
||||
let result
|
||||
|
||||
beforeEach(async function () {
|
||||
this.rclient.set = sinon.stub().resolves('OK')
|
||||
result = await this.CompileManager._checkIfRecentlyCompiled(
|
||||
this.project_id,
|
||||
this.user_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should try to set the key', function () {
|
||||
this.rclient.set
|
||||
.calledWith(
|
||||
`compile:${this.project_id}:${this.user_id}`,
|
||||
true,
|
||||
'EX',
|
||||
this.CompileManager.COMPILE_DELAY,
|
||||
'NX'
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve with false', function () {
|
||||
result.should.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_checkIfAutoCompileLimitHasBeenHit', function () {
|
||||
it('should be able to compile if it is not an autocompile', async function () {
|
||||
const canCompile =
|
||||
await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
false,
|
||||
'everyone'
|
||||
)
|
||||
expect(canCompile).to.equal(true)
|
||||
})
|
||||
|
||||
it('should be able to compile if rate limit has remaining', async function () {
|
||||
const canCompile =
|
||||
await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone'
|
||||
)
|
||||
|
||||
expect(this.rateLimiter.consume).to.have.been.calledWith('global')
|
||||
expect(canCompile).to.equal(true)
|
||||
})
|
||||
|
||||
it('should be not able to compile if rate limit has no remianing', async function () {
|
||||
this.rateLimiter.consume.rejects({ remainingPoints: 0 })
|
||||
const canCompile =
|
||||
await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone'
|
||||
)
|
||||
|
||||
expect(canCompile).to.equal(false)
|
||||
})
|
||||
|
||||
it('should return false if there is an error in the rate limit', async function () {
|
||||
this.rateLimiter.consume.rejects(new Error('BOOM!'))
|
||||
const canCompile =
|
||||
await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
|
||||
true,
|
||||
'everyone'
|
||||
)
|
||||
|
||||
expect(canCompile).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('wordCount', function () {
|
||||
let result
|
||||
const wordCount = 1
|
||||
|
||||
beforeEach(async function () {
|
||||
this.CompileManager.promises.getProjectCompileLimits = sinon
|
||||
.stub()
|
||||
.resolves((this.limits = { compileGroup: 'mock-compile-group' }))
|
||||
this.ClsiManager.promises.wordCount = sinon.stub().resolves(wordCount)
|
||||
result = await this.CompileManager.promises.wordCount(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should look up the compile group to use', function () {
|
||||
this.CompileManager.promises.getProjectCompileLimits
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call wordCount for project', function () {
|
||||
this.ClsiManager.promises.wordCount
|
||||
.calledWith(this.project_id, this.user_id, false, this.limits)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should resolve with the wordCount from the ClsiManager', function () {
|
||||
expect(result).to.equal(wordCount)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -252,9 +252,12 @@ describe('EditorHttpController', function () {
|
||||
default: ctx.SplitTestHandler,
|
||||
})
|
||||
)
|
||||
vi.doMock('../../../../app/src/Features/Compile/CompileManager.js', () => ({
|
||||
default: {},
|
||||
}))
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Compile/CompileManager.mjs',
|
||||
() => ({
|
||||
default: {},
|
||||
})
|
||||
)
|
||||
vi.doMock('../../../../app/src/Features/User/UserGetter.js', () => ({
|
||||
default: ctx.UserGetter,
|
||||
}))
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const modulePath = '../../../../app/src/Features/SamlLog/SamlLogHandler'
|
||||
const modulePath = '../../../../app/src/Features/SamlLog/SamlLogHandler.mjs'
|
||||
|
||||
describe('SamlLogHandler', function () {
|
||||
let SamlLog, SamlLogHandler, SamlLogModel
|
||||
let SamlLog, SamlLogHandler
|
||||
|
||||
let data, providerId, samlLog, sessionId
|
||||
|
||||
beforeEach(function () {
|
||||
beforeEach(async function (ctx) {
|
||||
samlLog = {
|
||||
save: sinon.stub(),
|
||||
}
|
||||
SamlLog = function () {
|
||||
return samlLog
|
||||
}
|
||||
SamlLogModel = { SamlLog }
|
||||
SamlLogHandler = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../../models/SamlLog': SamlLogModel,
|
||||
},
|
||||
})
|
||||
|
||||
ctx.logger = {
|
||||
error: sinon.stub(),
|
||||
}
|
||||
vi.doMock('@overleaf/logger', () => ({
|
||||
default: ctx.logger,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/models/SamlLog', () => ({ SamlLog }))
|
||||
|
||||
SamlLogHandler = (await import(modulePath)).default
|
||||
|
||||
data = { foo: true }
|
||||
providerId = 'provider-id'
|
||||
@@ -69,13 +73,13 @@ describe('SamlLogHandler', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should log without data and log error', function () {
|
||||
it('should log without data and log error', function (ctx) {
|
||||
samlLog.providerId.should.equal(providerId)
|
||||
samlLog.sessionId.should.equal(sessionId.substr(0, 8))
|
||||
expect(samlLog.data).to.be.undefined
|
||||
expect(samlLog.jsonData).to.be.undefined
|
||||
samlLog.save.should.have.been.calledOnce
|
||||
this.logger.error.should.have.been.calledOnce.and.calledWithMatch(
|
||||
ctx.logger.error.should.have.been.calledOnce.and.calledWithMatch(
|
||||
{ providerId, sessionId: sessionId.substr(0, 8) },
|
||||
'SamlLog JSON.stringify Error'
|
||||
)
|
||||
@@ -99,8 +103,8 @@ describe('SamlLogHandler', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should log error', function () {
|
||||
this.logger.error.should.have.been.calledOnce.and.calledWithMatch(
|
||||
it('should log error', function (ctx) {
|
||||
ctx.logger.error.should.have.been.calledOnce.and.calledWithMatch(
|
||||
{
|
||||
err,
|
||||
sessionId: sessionId.substr(0, 8),
|
||||
@@ -127,8 +131,8 @@ describe('SamlLogHandler', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should log error', function () {
|
||||
this.logger.error.should.have.been.calledOnce.and.calledWithMatch(
|
||||
it('should log error', function (ctx) {
|
||||
ctx.logger.error.should.have.been.calledOnce.and.calledWithMatch(
|
||||
{
|
||||
err,
|
||||
sessionId: sessionId.substr(0, 8),
|
||||
@@ -155,8 +159,8 @@ describe('SamlLogHandler', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should not log any error', function () {
|
||||
this.logger.error.should.not.have.been.called
|
||||
it('should not log any error', function (ctx) {
|
||||
ctx.logger.error.should.not.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
import { vi } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
|
||||
const modulePath =
|
||||
'../../../../app/src/Features/SystemMessages/SystemMessageManager.mjs'
|
||||
|
||||
describe('SystemMessageManager', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.messages = ['messages-stub']
|
||||
ctx.SystemMessage = {
|
||||
find: sinon.stub().returns({
|
||||
exec: sinon.stub().resolves(ctx.messages),
|
||||
}),
|
||||
}
|
||||
|
||||
vi.doMock('../../../../app/src/models/SystemMessage', () => ({
|
||||
SystemMessage: ctx.SystemMessage,
|
||||
}))
|
||||
|
||||
ctx.SystemMessageManager = (await import(modulePath)).default
|
||||
})
|
||||
|
||||
it('should look the messages up in the database on import', function (ctx) {
|
||||
sinon.assert.called(ctx.SystemMessage.find)
|
||||
})
|
||||
|
||||
describe('getMessage', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.SystemMessageManager._cachedMessages = ctx.messages
|
||||
ctx.result = ctx.SystemMessageManager.getMessages()
|
||||
})
|
||||
|
||||
it('should return the messages', function (ctx) {
|
||||
ctx.result.should.equal(ctx.messages)
|
||||
})
|
||||
})
|
||||
|
||||
describe('clearMessages', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.SystemMessage.deleteMany = sinon.stub().returns({
|
||||
exec: sinon.stub().resolves(),
|
||||
})
|
||||
ctx.SystemMessageManager.promises.clearMessages()
|
||||
})
|
||||
|
||||
it('should remove the messages from the database', function (ctx) {
|
||||
ctx.SystemMessage.deleteMany.calledWith({}).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,50 +0,0 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const modulePath = require('path').join(
|
||||
__dirname,
|
||||
'../../../../app/src/Features/SystemMessages/SystemMessageManager.js'
|
||||
)
|
||||
|
||||
describe('SystemMessageManager', function () {
|
||||
beforeEach(function () {
|
||||
this.messages = ['messages-stub']
|
||||
this.SystemMessage = {
|
||||
find: sinon.stub().returns({
|
||||
exec: sinon.stub().resolves(this.messages),
|
||||
}),
|
||||
}
|
||||
this.SystemMessageManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../../models/SystemMessage': { SystemMessage: this.SystemMessage },
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should look the messages up in the database on import', function () {
|
||||
sinon.assert.called(this.SystemMessage.find)
|
||||
})
|
||||
|
||||
describe('getMessage', function () {
|
||||
beforeEach(function () {
|
||||
this.SystemMessageManager._cachedMessages = this.messages
|
||||
this.result = this.SystemMessageManager.getMessages()
|
||||
})
|
||||
|
||||
it('should return the messages', function () {
|
||||
this.result.should.equal(this.messages)
|
||||
})
|
||||
})
|
||||
|
||||
describe('clearMessages', function () {
|
||||
beforeEach(function () {
|
||||
this.SystemMessage.deleteMany = sinon.stub().returns({
|
||||
exec: sinon.stub().resolves(),
|
||||
})
|
||||
this.SystemMessageManager.promises.clearMessages()
|
||||
})
|
||||
|
||||
it('should remove the messages from the database', function () {
|
||||
this.SystemMessage.deleteMany.calledWith({}).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,412 @@
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import { Writable } from 'stream'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/UpdateMerger.mjs'
|
||||
|
||||
describe('UpdateMerger :', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.projectId = 'project_id_here'
|
||||
ctx.userId = 'mock-user-id'
|
||||
ctx.randomUUID = 'random-uuid'
|
||||
ctx.dumpPath = '/dump'
|
||||
|
||||
ctx.docPath = ctx.newDocPath = '/folder/doc.tex'
|
||||
ctx.filePath = ctx.newFilePath = '/folder/file.png'
|
||||
|
||||
ctx.existingDocPath = '/folder/other.tex'
|
||||
ctx.existingFilePath = '/folder/fig1.pdf'
|
||||
|
||||
ctx.linkedFileData = { provider: 'url' }
|
||||
|
||||
ctx.existingDocs = [{ path: '/main.tex' }, { path: '/folder/other.tex' }]
|
||||
ctx.existingFiles = [{ path: '/figure.pdf' }, { path: '/folder/fig1.pdf' }]
|
||||
|
||||
ctx.fsPath = `${ctx.dumpPath}/${ctx.projectId}_${ctx.randomUUID}`
|
||||
ctx.fileContents = `\\documentclass{article}
|
||||
\\usepackage[utf8]{inputenc}
|
||||
|
||||
\\title{42}
|
||||
\\author{Jane Doe}
|
||||
\\date{June 2011}`
|
||||
ctx.docLines = ctx.fileContents.split('\n')
|
||||
ctx.source = 'dropbox'
|
||||
ctx.updateRequest = new Writable()
|
||||
ctx.writeStream = new Writable()
|
||||
|
||||
ctx.fsPromises = {
|
||||
unlink: sinon.stub().resolves(),
|
||||
readFile: sinon.stub().withArgs(ctx.fsPath).resolves(ctx.fileContents),
|
||||
mkdir: sinon.stub().resolves(),
|
||||
}
|
||||
|
||||
ctx.fs = {
|
||||
createWriteStream: sinon.stub().returns(ctx.writeStream),
|
||||
}
|
||||
|
||||
ctx.doc = {
|
||||
_id: new ObjectId(),
|
||||
rev: 2,
|
||||
}
|
||||
|
||||
ctx.file = {
|
||||
_id: new ObjectId(),
|
||||
rev: 6,
|
||||
}
|
||||
|
||||
ctx.folder = {
|
||||
_id: new ObjectId(),
|
||||
}
|
||||
|
||||
ctx.EditorController = {
|
||||
promises: {
|
||||
deleteEntityWithPath: sinon.stub().resolves(new ObjectId()),
|
||||
upsertDocWithPath: sinon
|
||||
.stub()
|
||||
.resolves({ doc: ctx.doc, folder: ctx.folder }),
|
||||
upsertFileWithPath: sinon
|
||||
.stub()
|
||||
.resolves({ file: ctx.file, folder: ctx.folder }),
|
||||
},
|
||||
}
|
||||
|
||||
ctx.FileTypeManager = {
|
||||
promises: {
|
||||
getType: sinon.stub(),
|
||||
},
|
||||
}
|
||||
|
||||
ctx.crypto = {
|
||||
randomUUID: sinon.stub().returns(ctx.randomUUID),
|
||||
}
|
||||
|
||||
ctx.ProjectEntityHandler = {
|
||||
promises: {
|
||||
getAllEntities: sinon.stub().resolves({
|
||||
docs: ctx.existingDocs,
|
||||
files: ctx.existingFiles,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
ctx.Settings = { path: { dumpFolder: ctx.dumpPath } }
|
||||
|
||||
ctx.stream = { pipeline: sinon.stub().resolves() }
|
||||
|
||||
vi.doMock('fs/promises', () => ({
|
||||
default: ctx.fsPromises,
|
||||
}))
|
||||
|
||||
vi.doMock('fs', () => ({
|
||||
default: ctx.fs,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Editor/EditorController', () => ({
|
||||
default: ctx.EditorController,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Uploads/FileTypeManager', () => ({
|
||||
default: ctx.FileTypeManager,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectEntityHandler',
|
||||
() => ({
|
||||
default: ctx.ProjectEntityHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: ctx.Settings,
|
||||
}))
|
||||
|
||||
vi.doMock('stream/promises', () => ({
|
||||
pipeline: ctx.stream.pipeline,
|
||||
}))
|
||||
|
||||
vi.doMock('crypto', () => ({
|
||||
default: ctx.crypto,
|
||||
}))
|
||||
|
||||
ctx.UpdateMerger = (await import(MODULE_PATH)).default
|
||||
})
|
||||
|
||||
describe('mergeUpdate', function () {
|
||||
describe('doc updates for a new doc', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.FileTypeManager.promises.getType.resolves({
|
||||
binary: false,
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
ctx.mergeUpdateResult = await ctx.UpdateMerger.promises.mergeUpdate(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.docPath,
|
||||
ctx.updateRequest,
|
||||
ctx.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function (ctx) {
|
||||
expect(ctx.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as doc', function (ctx) {
|
||||
expect(
|
||||
ctx.EditorController.promises.upsertDocWithPath
|
||||
).to.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.docPath,
|
||||
ctx.docLines,
|
||||
ctx.source,
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function (ctx) {
|
||||
expect(ctx.fsPromises.unlink).to.have.been.calledWith(ctx.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function (ctx) {
|
||||
expect(ctx.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(ctx.mergeUpdateResult.rev).to.equal(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('file updates for a new file ', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.FileTypeManager.promises.getType.resolves({ binary: true })
|
||||
ctx.mergeUpdateResult = await ctx.UpdateMerger.promises.mergeUpdate(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.filePath,
|
||||
ctx.updateRequest,
|
||||
ctx.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function (ctx) {
|
||||
expect(ctx.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as file', function (ctx) {
|
||||
expect(
|
||||
ctx.EditorController.promises.upsertFileWithPath
|
||||
).to.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.filePath,
|
||||
ctx.fsPath,
|
||||
null,
|
||||
ctx.source,
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function (ctx) {
|
||||
expect(ctx.fsPromises.unlink).to.have.been.calledWith(ctx.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function (ctx) {
|
||||
expect(ctx.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(ctx.mergeUpdateResult.rev).to.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doc updates for an existing doc', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.FileTypeManager.promises.getType.resolves({
|
||||
binary: false,
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
ctx.mergeUpdateResult = await ctx.UpdateMerger.promises.mergeUpdate(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.existingDocPath,
|
||||
ctx.updateRequest,
|
||||
ctx.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function (ctx) {
|
||||
expect(ctx.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as doc', function (ctx) {
|
||||
expect(
|
||||
ctx.EditorController.promises.upsertDocWithPath
|
||||
).to.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.existingDocPath,
|
||||
ctx.docLines,
|
||||
ctx.source,
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function (ctx) {
|
||||
expect(ctx.fsPromises.unlink).to.have.been.calledWith(ctx.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function (ctx) {
|
||||
expect(ctx.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(ctx.mergeUpdateResult.rev).to.equal(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('file updates for an existing file', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.FileTypeManager.promises.getType.resolves({ binary: true })
|
||||
ctx.mergeUpdateResult = await ctx.UpdateMerger.promises.mergeUpdate(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.existingFilePath,
|
||||
ctx.updateRequest,
|
||||
ctx.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function (ctx) {
|
||||
expect(ctx.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as file', function (ctx) {
|
||||
expect(
|
||||
ctx.EditorController.promises.upsertFileWithPath
|
||||
).to.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.existingFilePath,
|
||||
ctx.fsPath,
|
||||
null,
|
||||
ctx.source,
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function (ctx) {
|
||||
expect(ctx.fsPromises.unlink).to.have.been.calledWith(ctx.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function (ctx) {
|
||||
expect(ctx.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(ctx.mergeUpdateResult.rev).to.equal(6)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('file updates for an existing doc', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.FileTypeManager.promises.getType.resolves({ binary: true })
|
||||
ctx.mergeUpdateResult = await ctx.UpdateMerger.promises.mergeUpdate(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.existingDocPath,
|
||||
ctx.updateRequest,
|
||||
ctx.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function (ctx) {
|
||||
expect(ctx.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as file', function (ctx) {
|
||||
expect(
|
||||
ctx.EditorController.promises.upsertFileWithPath
|
||||
).to.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.existingDocPath,
|
||||
ctx.fsPath,
|
||||
null,
|
||||
ctx.source,
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function (ctx) {
|
||||
expect(ctx.fsPromises.unlink).to.have.been.calledWith(ctx.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function (ctx) {
|
||||
expect(ctx.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(ctx.mergeUpdateResult.rev).to.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doc updates for an existing file', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.FileTypeManager.promises.getType.resolves({ binary: true })
|
||||
ctx.mergeUpdateResult = await ctx.UpdateMerger.promises.mergeUpdate(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.existingFilePath,
|
||||
ctx.updateRequest,
|
||||
ctx.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function (ctx) {
|
||||
expect(ctx.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should not delete the existing file', function (ctx) {
|
||||
expect(ctx.EditorController.promises.deleteEntityWithPath).to.not.have
|
||||
.been.called
|
||||
})
|
||||
|
||||
it('should process update as file', function (ctx) {
|
||||
expect(
|
||||
ctx.EditorController.promises.upsertFileWithPath
|
||||
).to.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.existingFilePath,
|
||||
ctx.fsPath,
|
||||
null,
|
||||
ctx.source,
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function (ctx) {
|
||||
expect(ctx.fsPromises.unlink).to.have.been.calledWith(ctx.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function (ctx) {
|
||||
expect(ctx.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(ctx.mergeUpdateResult.rev).to.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteUpdate', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.deleteUpdateResult = await ctx.UpdateMerger.promises.deleteUpdate(
|
||||
ctx.userId,
|
||||
ctx.projectId,
|
||||
ctx.docPath,
|
||||
ctx.source
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(function (ctx) {
|
||||
delete ctx.deleteUpdateResult
|
||||
})
|
||||
|
||||
it('should delete the entity in the editor controller', function (ctx) {
|
||||
expect(
|
||||
ctx.EditorController.promises.deleteEntityWithPath
|
||||
).to.have.been.calledWith(
|
||||
ctx.projectId,
|
||||
ctx.docPath,
|
||||
ctx.source,
|
||||
ctx.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('returns the entity id', function (ctx) {
|
||||
expect(ctx.deleteUpdateResult).to.be.instanceOf(ObjectId)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,387 +0,0 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const { Writable } = require('stream')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/UpdateMerger.js'
|
||||
|
||||
describe('UpdateMerger :', function () {
|
||||
beforeEach(function () {
|
||||
this.projectId = 'project_id_here'
|
||||
this.userId = 'mock-user-id'
|
||||
this.randomUUID = 'random-uuid'
|
||||
this.dumpPath = '/dump'
|
||||
|
||||
this.docPath = this.newDocPath = '/folder/doc.tex'
|
||||
this.filePath = this.newFilePath = '/folder/file.png'
|
||||
|
||||
this.existingDocPath = '/folder/other.tex'
|
||||
this.existingFilePath = '/folder/fig1.pdf'
|
||||
|
||||
this.linkedFileData = { provider: 'url' }
|
||||
|
||||
this.existingDocs = [{ path: '/main.tex' }, { path: '/folder/other.tex' }]
|
||||
this.existingFiles = [{ path: '/figure.pdf' }, { path: '/folder/fig1.pdf' }]
|
||||
|
||||
this.fsPath = `${this.dumpPath}/${this.projectId}_${this.randomUUID}`
|
||||
this.fileContents = `\\documentclass{article}
|
||||
\\usepackage[utf8]{inputenc}
|
||||
|
||||
\\title{42}
|
||||
\\author{Jane Doe}
|
||||
\\date{June 2011}`
|
||||
this.docLines = this.fileContents.split('\n')
|
||||
this.source = 'dropbox'
|
||||
this.updateRequest = new Writable()
|
||||
this.writeStream = new Writable()
|
||||
|
||||
this.fsPromises = {
|
||||
unlink: sinon.stub().resolves(),
|
||||
readFile: sinon.stub().withArgs(this.fsPath).resolves(this.fileContents),
|
||||
mkdir: sinon.stub().resolves(),
|
||||
}
|
||||
|
||||
this.fs = {
|
||||
createWriteStream: sinon.stub().returns(this.writeStream),
|
||||
}
|
||||
|
||||
this.doc = {
|
||||
_id: new ObjectId(),
|
||||
rev: 2,
|
||||
}
|
||||
|
||||
this.file = {
|
||||
_id: new ObjectId(),
|
||||
rev: 6,
|
||||
}
|
||||
|
||||
this.folder = {
|
||||
_id: new ObjectId(),
|
||||
}
|
||||
|
||||
this.EditorController = {
|
||||
promises: {
|
||||
deleteEntityWithPath: sinon.stub().resolves(new ObjectId()),
|
||||
upsertDocWithPath: sinon
|
||||
.stub()
|
||||
.resolves({ doc: this.doc, folder: this.folder }),
|
||||
upsertFileWithPath: sinon
|
||||
.stub()
|
||||
.resolves({ file: this.file, folder: this.folder }),
|
||||
},
|
||||
}
|
||||
|
||||
this.FileTypeManager = {
|
||||
promises: {
|
||||
getType: sinon.stub(),
|
||||
},
|
||||
}
|
||||
|
||||
this.crypto = {
|
||||
randomUUID: sinon.stub().returns(this.randomUUID),
|
||||
}
|
||||
|
||||
this.ProjectEntityHandler = {
|
||||
promises: {
|
||||
getAllEntities: sinon.stub().resolves({
|
||||
docs: this.existingDocs,
|
||||
files: this.existingFiles,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
this.Settings = { path: { dumpFolder: this.dumpPath } }
|
||||
|
||||
this.stream = { pipeline: sinon.stub().resolves() }
|
||||
|
||||
this.UpdateMerger = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'fs/promises': this.fsPromises,
|
||||
fs: this.fs,
|
||||
'../Editor/EditorController': this.EditorController,
|
||||
'../Uploads/FileTypeManager': this.FileTypeManager,
|
||||
'../Project/ProjectEntityHandler': this.ProjectEntityHandler,
|
||||
'@overleaf/settings': this.Settings,
|
||||
'stream/promises': this.stream,
|
||||
crypto: this.crypto,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('mergeUpdate', function () {
|
||||
describe('doc updates for a new doc', function () {
|
||||
beforeEach(async function () {
|
||||
this.FileTypeManager.promises.getType.resolves({
|
||||
binary: false,
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
this.mergeUpdateResult = await this.UpdateMerger.promises.mergeUpdate(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.docPath,
|
||||
this.updateRequest,
|
||||
this.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function () {
|
||||
expect(this.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as doc', function () {
|
||||
expect(
|
||||
this.EditorController.promises.upsertDocWithPath
|
||||
).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.docPath,
|
||||
this.docLines,
|
||||
this.source,
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function () {
|
||||
expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function () {
|
||||
expect(this.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(this.mergeUpdateResult.rev).to.equal(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('file updates for a new file ', function () {
|
||||
beforeEach(async function () {
|
||||
this.FileTypeManager.promises.getType.resolves({ binary: true })
|
||||
this.mergeUpdateResult = await this.UpdateMerger.promises.mergeUpdate(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.filePath,
|
||||
this.updateRequest,
|
||||
this.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function () {
|
||||
expect(this.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as file', function () {
|
||||
expect(
|
||||
this.EditorController.promises.upsertFileWithPath
|
||||
).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.filePath,
|
||||
this.fsPath,
|
||||
null,
|
||||
this.source,
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function () {
|
||||
expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function () {
|
||||
expect(this.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(this.mergeUpdateResult.rev).to.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doc updates for an existing doc', function () {
|
||||
beforeEach(async function () {
|
||||
this.FileTypeManager.promises.getType.resolves({
|
||||
binary: false,
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
this.mergeUpdateResult = await this.UpdateMerger.promises.mergeUpdate(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.existingDocPath,
|
||||
this.updateRequest,
|
||||
this.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function () {
|
||||
expect(this.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as doc', function () {
|
||||
expect(
|
||||
this.EditorController.promises.upsertDocWithPath
|
||||
).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.existingDocPath,
|
||||
this.docLines,
|
||||
this.source,
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function () {
|
||||
expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function () {
|
||||
expect(this.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(this.mergeUpdateResult.rev).to.equal(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('file updates for an existing file', function () {
|
||||
beforeEach(async function () {
|
||||
this.FileTypeManager.promises.getType.resolves({ binary: true })
|
||||
this.mergeUpdateResult = await this.UpdateMerger.promises.mergeUpdate(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.existingFilePath,
|
||||
this.updateRequest,
|
||||
this.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function () {
|
||||
expect(this.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as file', function () {
|
||||
expect(
|
||||
this.EditorController.promises.upsertFileWithPath
|
||||
).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.existingFilePath,
|
||||
this.fsPath,
|
||||
null,
|
||||
this.source,
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function () {
|
||||
expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function () {
|
||||
expect(this.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(this.mergeUpdateResult.rev).to.equal(6)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('file updates for an existing doc', function () {
|
||||
beforeEach(async function () {
|
||||
this.FileTypeManager.promises.getType.resolves({ binary: true })
|
||||
this.mergeUpdateResult = await this.UpdateMerger.promises.mergeUpdate(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.existingDocPath,
|
||||
this.updateRequest,
|
||||
this.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function () {
|
||||
expect(this.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should process update as file', function () {
|
||||
expect(
|
||||
this.EditorController.promises.upsertFileWithPath
|
||||
).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.existingDocPath,
|
||||
this.fsPath,
|
||||
null,
|
||||
this.source,
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function () {
|
||||
expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function () {
|
||||
expect(this.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(this.mergeUpdateResult.rev).to.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doc updates for an existing file', function () {
|
||||
beforeEach(async function () {
|
||||
this.FileTypeManager.promises.getType.resolves({ binary: true })
|
||||
this.mergeUpdateResult = await this.UpdateMerger.promises.mergeUpdate(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.existingFilePath,
|
||||
this.updateRequest,
|
||||
this.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should look at the file contents', function () {
|
||||
expect(this.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should not delete the existing file', function () {
|
||||
expect(this.EditorController.promises.deleteEntityWithPath).to.not.have
|
||||
.been.called
|
||||
})
|
||||
|
||||
it('should process update as file', function () {
|
||||
expect(
|
||||
this.EditorController.promises.upsertFileWithPath
|
||||
).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.existingFilePath,
|
||||
this.fsPath,
|
||||
null,
|
||||
this.source,
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the temp file from disk', function () {
|
||||
expect(this.fsPromises.unlink).to.have.been.calledWith(this.fsPath)
|
||||
})
|
||||
|
||||
it('returns the entity id and rev', function () {
|
||||
expect(this.mergeUpdateResult.entityId).to.be.instanceOf(ObjectId)
|
||||
expect(this.mergeUpdateResult.rev).to.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteUpdate', function () {
|
||||
beforeEach(async function () {
|
||||
this.deleteUpdateResult = await this.UpdateMerger.promises.deleteUpdate(
|
||||
this.userId,
|
||||
this.projectId,
|
||||
this.docPath,
|
||||
this.source
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
delete this.deleteUpdateResult
|
||||
})
|
||||
|
||||
it('should delete the entity in the editor controller', function () {
|
||||
expect(
|
||||
this.EditorController.promises.deleteEntityWithPath
|
||||
).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.docPath,
|
||||
this.source,
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('returns the entity id', function () {
|
||||
expect(this.deleteUpdateResult).to.be.instanceOf(ObjectId)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,19 +1,20 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
import { vi, expect } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const MODULE_PATH = '../../../../app/src/Features/Tutorial/TutorialHandler'
|
||||
|
||||
describe('TutorialHandler', function () {
|
||||
beforeEach(function () {
|
||||
this.clock = sinon.useFakeTimers()
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.clock = sinon.useFakeTimers()
|
||||
|
||||
const THIRTY_DAYS_AGO = Date.now() - 30 * 24 * 60 * 60 * 1000
|
||||
const TOMORROW = Date.now() + 24 * 60 * 60 * 1000
|
||||
const YESTERDAY = Date.now() - 24 * 60 * 60 * 1000
|
||||
|
||||
this.user = {
|
||||
ctx.user = {
|
||||
_id: new ObjectId(),
|
||||
completedTutorials: {
|
||||
'legacy-format': new Date(Date.now() - 1000),
|
||||
@@ -42,28 +43,26 @@ describe('TutorialHandler', function () {
|
||||
},
|
||||
}
|
||||
|
||||
this.UserUpdater = {
|
||||
ctx.UserUpdater = {
|
||||
promises: {
|
||||
updateUser: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.TutorialHandler = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'../User/UserUpdater': this.UserUpdater,
|
||||
},
|
||||
})
|
||||
vi.doMock('../../../../app/src/Features/User/UserUpdater', () => ({
|
||||
default: ctx.UserUpdater,
|
||||
}))
|
||||
|
||||
ctx.TutorialHandler = (await import(MODULE_PATH)).default
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.clock.restore()
|
||||
afterEach(function (ctx) {
|
||||
ctx.clock.restore()
|
||||
})
|
||||
|
||||
describe('getInactiveTutorials', function () {
|
||||
it('returns all recorded tutorials except when they were posponed long ago', function () {
|
||||
const hiddenTutorials = this.TutorialHandler.getInactiveTutorials(
|
||||
this.user
|
||||
)
|
||||
it('returns all recorded tutorials except when they were posponed long ago', function (ctx) {
|
||||
const hiddenTutorials = ctx.TutorialHandler.getInactiveTutorials(ctx.user)
|
||||
expect(hiddenTutorials).to.have.members([
|
||||
'legacy-format',
|
||||
'completed',
|
||||
@@ -73,7 +72,7 @@ describe('TutorialHandler', function () {
|
||||
|
||||
expect(hiddenTutorials).to.have.lengthOf(4)
|
||||
|
||||
const shownTutorials = Object.keys(this.user.completedTutorials).filter(
|
||||
const shownTutorials = Object.keys(ctx.user.completedTutorials).filter(
|
||||
key => !hiddenTutorials.includes(key)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user