[web] Convert some Features files to ES modules (part 2) (#28275)

* Rename files

* Rename test files

* Convert to ESM

GitOrigin-RevId: b0ee442ac8edd4ef3695f93a91ffd9521e6bf259
This commit is contained in:
Antoine Clausse
2025-09-15 12:44:00 +02:00
committed by Copybot
parent a2c0b66115
commit 723954ccc2
29 changed files with 1006 additions and 931 deletions

View File

@@ -13,7 +13,7 @@
*/
import OError from '@overleaf/o-error'
import ProjectGetter from '../Project/ProjectGetter.js'
import ProjectHistoryHandler from '../Project/ProjectHistoryHandler.js'
import ProjectHistoryHandler from '../Project/ProjectHistoryHandler.mjs'
import ProjectLocator from '../Project/ProjectLocator.js'
import ProjectRootDocManager from '../Project/ProjectRootDocManager.js'
import UserGetter from '../User/UserGetter.js'

View File

@@ -1,7 +1,7 @@
const { callbackify } = require('util')
const Path = require('path')
const ProjectEntityHandler = require('./ProjectEntityHandler')
const EditorController = require('../Editor/EditorController')
import { callbackify } from 'node:util'
import Path from 'node:path'
import ProjectEntityHandler from './ProjectEntityHandler.js'
import EditorController from '../Editor/EditorController.js'
// generate a new name based on the original, with an optional label.
// e.g. origname-20210101-122345.tex (default)
@@ -36,7 +36,7 @@ async function restoreDeletedDoc(projectId, docId, docName, userId) {
)
}
module.exports = {
export default {
restoreDeletedDoc: callbackify(restoreDeletedDoc),
generateRestoredName,
promises: {

View File

@@ -1,8 +1,8 @@
const { Project } = require('../../models/Project')
const ProjectDetailsHandler = require('./ProjectDetailsHandler')
const HistoryManager = require('../History/HistoryManager')
const ProjectEntityUpdateHandler = require('./ProjectEntityUpdateHandler')
const { callbackify } = require('util')
import { Project } from '../../models/Project.js'
import ProjectDetailsHandler from './ProjectDetailsHandler.js'
import HistoryManager from '../History/HistoryManager.js'
import ProjectEntityUpdateHandler from './ProjectEntityUpdateHandler.js'
import { callbackify } from 'node:util'
const ProjectHistoryHandler = {
async setHistoryId(projectId, historyId) {
@@ -55,7 +55,7 @@ const ProjectHistoryHandler = {
},
}
module.exports = {
export default {
setHistoryId: callbackify(ProjectHistoryHandler.setHistoryId),
getHistoryId: callbackify(ProjectHistoryHandler.getHistoryId),
ensureHistoryExistsForProject: callbackify(

View File

@@ -1,7 +1,7 @@
const OError = require('@overleaf/o-error')
const { User } = require('../../models/User')
const FeaturesUpdater = require('../Subscription/FeaturesUpdater')
const { callbackify } = require('@overleaf/promise-utils')
import OError from '@overleaf/o-error'
import { User } from '../../models/User.js'
import FeaturesUpdater from '../Subscription/FeaturesUpdater.js'
import { callbackify } from '@overleaf/promise-utils'
async function allocate(referalId, newUserId, referalSource, referalMedium) {
if (referalId == null) {
@@ -40,7 +40,7 @@ async function allocate(referalId, newUserId, referalSource, referalMedium) {
}
}
module.exports = {
export default {
allocate: callbackify(allocate),
promises: {
allocate,

View File

@@ -1,11 +1,11 @@
const logger = require('@overleaf/logger')
const http = require('http')
const https = require('https')
const Settings = require('@overleaf/settings')
const TpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender')
const TpdsProjectFlusher = require('../ThirdPartyDataStore/TpdsProjectFlusher')
const EditorRealTimeController = require('../Editor/EditorRealTimeController')
const SystemMessageManager = require('../SystemMessages/SystemMessageManager')
import logger from '@overleaf/logger'
import http from 'node:http'
import https from 'node:https'
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'
const AdminController = {
_sendDisconnectAllUsersMessage: delay => {
@@ -94,4 +94,4 @@ const AdminController = {
},
}
module.exports = AdminController
export default AdminController

View File

@@ -1,8 +1,8 @@
const logger = require('@overleaf/logger')
const Settings = require('@overleaf/settings')
const { IncomingWebhook } = require('@slack/webhook')
const moment = require('moment')
const SplitTestUtils = require('./SplitTestUtils')
import logger from '@overleaf/logger'
import Settings from '@overleaf/settings'
import { IncomingWebhook } from '@slack/webhook'
import moment from 'moment'
import SplitTestUtils from './SplitTestUtils.js'
async function sendNotification(splitTest, action, user) {
const lastVersion = SplitTestUtils.getCurrentVersion(splitTest)
@@ -60,6 +60,6 @@ async function sendNotification(splitTest, action, user) {
}
}
module.exports = {
export default {
sendNotification,
}

View File

@@ -1,7 +1,7 @@
const SplitTestHandler = require('./SplitTestHandler')
const logger = require('@overleaf/logger')
const { expressify } = require('@overleaf/promise-utils')
const Errors = require('../Errors/Errors')
import SplitTestHandler from './SplitTestHandler.js'
import logger from '@overleaf/logger'
import { expressify } from '@overleaf/promise-utils'
import Errors from '../Errors/Errors.js'
function loadAssignmentsInLocals(splitTestNames) {
return async function (req, res, next) {
@@ -43,7 +43,7 @@ function ensureSplitTestEnabledForUser(
})
}
module.exports = {
export default {
loadAssignmentsInLocals,
ensureSplitTestEnabledForUser,
}

View File

@@ -9,6 +9,6 @@ function getProviderId(subscriptionId) {
return `ol-group-subscription-id:${subscriptionId.toString()}`
}
module.exports = {
export default {
getProviderId,
}

View File

@@ -1,4 +1,4 @@
import SubscriptionGroupHandler from './SubscriptionGroupHandler.js'
import SubscriptionGroupHandler from './SubscriptionGroupHandler.mjs'
import OError from '@overleaf/o-error'
import logger from '@overleaf/logger'

View File

@@ -1,26 +1,29 @@
const { callbackify } = require('util')
const _ = require('lodash')
const OError = require('@overleaf/o-error')
const SubscriptionUpdater = require('./SubscriptionUpdater')
const SubscriptionLocator = require('./SubscriptionLocator')
const SubscriptionController = require('./SubscriptionController')
const SubscriptionHelper = require('./SubscriptionHelper')
const { Subscription } = require('../../models/Subscription')
const { User } = require('../../models/User')
const PlansLocator = require('./PlansLocator')
const TeamInvitesHandler = require('./TeamInvitesHandler')
const GroupPlansData = require('./GroupPlansData')
const Modules = require('../../infrastructure/Modules')
const { MEMBERS_LIMIT_ADD_ON_CODE } = require('./PaymentProviderEntities')
const {
import { callbackify } from 'node:util'
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 SubscriptionHelper from './SubscriptionHelper.js'
import { Subscription } from '../../models/Subscription.js'
import { User } from '../../models/User.js'
import PlansLocator from './PlansLocator.js'
import TeamInvitesHandler from './TeamInvitesHandler.js'
import GroupPlansData from './GroupPlansData.js'
import Modules from '../../infrastructure/Modules.js'
import PaymentProviderEntities from './PaymentProviderEntities.js'
import {
ManuallyCollectedError,
PendingChangeError,
InactiveError,
HasPastDueInvoiceError,
HasNoAdditionalLicenseWhenManuallyCollectedError,
} = require('./Errors')
const EmailHelper = require('../Helpers/EmailHelper')
const { InvalidEmailError } = require('../Errors/Errors')
} from './Errors.js'
import EmailHelper from '../Helpers/EmailHelper.js'
import { InvalidEmailError } from '../Errors/Errors.js'
const MEMBERS_LIMIT_ADD_ON_CODE =
PaymentProviderEntities.MEMBERS_LIMIT_ADD_ON_CODE
async function removeUserFromGroup(subscriptionId, userIdToRemove, auditLog) {
await SubscriptionUpdater.promises.removeUserFromGroup(
@@ -470,7 +473,7 @@ async function updateGroupMembersBulk(
return result
}
module.exports = {
export default {
removeUserFromGroup: callbackify(removeUserFromGroup),
replaceUserReferencesInGroups: callbackify(replaceUserReferencesInGroups),
ensureFlexibleLicensingEnabled: callbackify(ensureFlexibleLicensingEnabled),

View File

@@ -1,4 +1,4 @@
import SurveyManager from './SurveyManager.js'
import SurveyManager from './SurveyManager.mjs'
import { Survey } from '../../models/Survey.js'
import { CacheLoader } from 'cache-flow'

View File

@@ -1,5 +1,5 @@
const { Survey } = require('../../models/Survey')
const OError = require('@overleaf/o-error')
import { Survey } from '../../models/Survey.js'
import OError from '@overleaf/o-error'
async function getSurvey() {
try {
@@ -67,7 +67,7 @@ async function deleteSurvey() {
}
}
module.exports = {
export default {
getSurvey,
updateSurvey,
deleteSurvey,

View File

@@ -1,6 +1,6 @@
const Settings = require('@overleaf/settings')
const SessionManager = require('../Authentication/SessionManager')
const SystemMessageManager = require('./SystemMessageManager')
import Settings from '@overleaf/settings'
import SessionManager from '../Authentication/SessionManager.js'
import SystemMessageManager from './SystemMessageManager.js'
const ProjectController = {
getMessages(req, res, next) {
@@ -24,4 +24,4 @@ const ProjectController = {
},
}
module.exports = ProjectController
export default ProjectController

View File

@@ -1,4 +1,4 @@
import AdminController from './Features/ServerAdmin/AdminController.js'
import AdminController from './Features/ServerAdmin/AdminController.mjs'
import ErrorController from './Features/Errors/ErrorController.mjs'
import Features from './infrastructure/Features.js'
import ProjectController from './Features/Project/ProjectController.mjs'
@@ -54,7 +54,7 @@ import TokenAccessRouter from './Features/TokenAccess/TokenAccessRouter.mjs'
import LinkedFilesRouter from './Features/LinkedFiles/LinkedFilesRouter.mjs'
import TemplatesRouter from './Features/Templates/TemplatesRouter.js'
import UserMembershipRouter from './Features/UserMembership/UserMembershipRouter.mjs'
import SystemMessageController from './Features/SystemMessages/SystemMessageController.js'
import SystemMessageController from './Features/SystemMessages/SystemMessageController.mjs'
import AnalyticsRegistrationSourceMiddleware from './Features/Analytics/AnalyticsRegistrationSourceMiddleware.js'
import AnalyticsUTMTrackingMiddleware from './Features/Analytics/AnalyticsUTMTrackingMiddleware.mjs'
import CaptchaMiddleware from './Features/Captcha/CaptchaMiddleware.mjs'

View File

@@ -2,7 +2,7 @@ import fs from 'node:fs'
import minimist from 'minimist'
import { parse } from 'csv'
import Stream from 'node:stream/promises'
import SubscriptionGroupHandler from '../app/src/Features/Subscription/SubscriptionGroupHandler.js'
import SubscriptionGroupHandler from '../app/src/Features/Subscription/SubscriptionGroupHandler.mjs'
import { Subscription } from '../app/src/models/Subscription.js'
import { InvalidEmailError } from '../app/src/Features/Errors/Errors.js'

View File

@@ -1,6 +1,6 @@
import { promisify } from 'node:util'
import Settings from '@overleaf/settings'
import AdminController from '../app/src/Features/ServerAdmin/AdminController.js'
import AdminController from '../app/src/Features/ServerAdmin/AdminController.mjs'
import minimist from 'minimist'
import { fileURLToPath } from 'node:url'
import { scriptRunner } from './lib/ScriptRunner.mjs'

View File

@@ -4,7 +4,7 @@ import minimist from 'minimist'
import { db, ObjectId } from '../app/src/infrastructure/mongodb.js'
import ProjectEntityUpdateHandler from '../app/src/Features/Project/ProjectEntityUpdateHandler.js'
import ProjectEntityRestoreHandler from '../app/src/Features/Project/ProjectEntityRestoreHandler.js'
import ProjectEntityRestoreHandler from '../app/src/Features/Project/ProjectEntityRestoreHandler.mjs'
import RedisWrapper from '@overleaf/redis-wrapper'
import Settings from '@overleaf/settings'
import logger from '@overleaf/logger'

View File

@@ -1,4 +1,4 @@
import ProjectEntityRestoreHandler from '../app/src/Features/Project/ProjectEntityRestoreHandler.js'
import ProjectEntityRestoreHandler from '../app/src/Features/Project/ProjectEntityRestoreHandler.mjs'
import ProjectEntityHandler from '../app/src/Features/Project/ProjectEntityHandler.js'
import DocstoreManager from '../app/src/Features/Docstore/DocstoreManager.js'
import { scriptRunner } from './lib/ScriptRunner.mjs'

View File

@@ -1,4 +1,4 @@
import ProjectEntityRestoreHandler from '../app/src/Features/Project/ProjectEntityRestoreHandler.js'
import ProjectEntityRestoreHandler from '../app/src/Features/Project/ProjectEntityRestoreHandler.mjs'
import DocstoreManager from '../app/src/Features/Docstore/DocstoreManager.js'
import { scriptRunner } from './lib/ScriptRunner.mjs'

View File

@@ -6,7 +6,7 @@ import { SSOConfig } from '../../../../app/src/models/SSOConfig.js'
import UserHelper from './UserHelper.mjs'
import SAMLHelper from './SAMLHelper.mjs'
import Settings from '@overleaf/settings'
import { getProviderId } from '../../../../app/src/Features/Subscription/GroupUtils.js'
import GroupUtils from '../../../../app/src/Features/Subscription/GroupUtils.mjs'
import UserGetter from '../../../../app/src/Features/User/UserGetter.js'
import { fileURLToPath } from 'node:url'
import { Subscription as SubscriptionModel } from '../../../../app/src/models/Subscription.js'
@@ -75,7 +75,7 @@ export async function createGroupSSO(
await subscription.ensureExists()
const subscriptionId = subscription._id.toString()
const enrollmentUrl = getEnrollmentUrl(subscriptionId)
const internalProviderId = getProviderId(subscriptionId)
const internalProviderId = GroupUtils.getProviderId(subscriptionId)
if (SSOConfigValidated) {
await linkGroupMember(
@@ -122,7 +122,7 @@ export async function linkGroupMember(
.exec()
const userIdAttribute = subscription?.ssoConfig?.userIdAttribute
const internalProviderId = getProviderId(groupId)
const internalProviderId = GroupUtils.getProviderId(groupId)
const enrollmentUrl = getEnrollmentUrl(groupId)
const userHelper = await UserHelper.loginUser(
{
@@ -189,7 +189,7 @@ export async function linkGroupMember(
}
export async function checkUserHasSSOLinked(userId, groupId) {
const internalProviderId = getProviderId(groupId)
const internalProviderId = GroupUtils.getProviderId(groupId)
const user = await UserGetter.promises.getUser(
{ _id: userId },
{ samlIdentifiers: 1, enrollment: 1 }

View File

@@ -0,0 +1,105 @@
import { vi, expect } from 'vitest'
import sinon from 'sinon'
const MODULE_PATH =
'../../../../app/src/Features/Project/ProjectEntityRestoreHandler.mjs'
describe('ProjectEntityRestoreHandler', function () {
beforeEach(async function (ctx) {
ctx.project = {
_id: '123213jlkj9kdlsaj',
}
ctx.user = {
_id: '588f3ddae8ebc1bac07c9fa4',
first_name: 'bjkdsjfk',
features: {},
}
ctx.docId = '4eecb1c1bffa66588e0000a2'
ctx.DocModel = class Doc {
constructor(options) {
this.name = options.name
this.lines = options.lines
this._id = this.docId
this.rev = 0
}
}
ctx.ProjectEntityHandler = {
promises: {
getDoc: sinon.stub(),
},
}
ctx.EditorController = {
promises: {
addDocWithRanges: sinon.stub(),
},
}
vi.doMock(
'../../../../app/src/Features/Project/ProjectEntityHandler.js',
() => ({
default: ctx.ProjectEntityHandler,
})
)
vi.doMock(
'../../../../app/src/Features/Editor/EditorController.js',
() => ({
default: ctx.EditorController,
})
)
ctx.ProjectEntityRestoreHandler = (await import(MODULE_PATH)).default
})
it('should add a new doc with timestamp name and old content', async function (ctx) {
const docName = 'deletedDoc'
ctx.docLines = ['line one', 'line two']
ctx.rev = 3
ctx.ranges = { comments: [{ id: 123 }] }
ctx.newDoc = new ctx.DocModel({
name: ctx.docName,
lines: undefined,
_id: ctx.docId,
rev: 0,
})
ctx.ProjectEntityHandler.promises.getDoc.resolves({
lines: ctx.docLines,
rev: ctx.rev,
version: 'version',
ranges: ctx.ranges,
})
ctx.EditorController.promises.addDocWithRanges = sinon
.stub()
.resolves(ctx.newDoc)
await ctx.ProjectEntityRestoreHandler.promises.restoreDeletedDoc(
ctx.project._id,
ctx.docId,
docName,
ctx.user._id
)
const docNameMatcher = new RegExp(docName + '-\\d{4}-\\d{2}-\\d{2}-\\d+')
expect(
ctx.EditorController.promises.addDocWithRanges
).to.have.been.calledWith(
ctx.project._id,
null,
sinon.match(docNameMatcher),
ctx.docLines,
ctx.ranges,
null,
ctx.user._id
)
})
})

View File

@@ -1,97 +0,0 @@
const { expect } = require('chai')
const sinon = require('sinon')
const SandboxedModule = require('sandboxed-module')
const MODULE_PATH =
'../../../../app/src/Features/Project/ProjectEntityRestoreHandler.js'
describe('ProjectEntityRestoreHandler', function () {
beforeEach(function () {
this.project = {
_id: '123213jlkj9kdlsaj',
}
this.user = {
_id: '588f3ddae8ebc1bac07c9fa4',
first_name: 'bjkdsjfk',
features: {},
}
this.docId = '4eecb1c1bffa66588e0000a2'
this.DocModel = class Doc {
constructor(options) {
this.name = options.name
this.lines = options.lines
this._id = this.docId
this.rev = 0
}
}
this.ProjectEntityHandler = {
promises: {
getDoc: sinon.stub(),
},
}
this.EditorController = {
promises: {
addDocWithRanges: sinon.stub(),
},
}
this.ProjectEntityRestoreHandler = SandboxedModule.require(MODULE_PATH, {
requires: {
'./ProjectEntityHandler': this.ProjectEntityHandler,
'../Editor/EditorController': this.EditorController,
},
})
})
it('should add a new doc with timestamp name and old content', async function () {
const docName = 'deletedDoc'
this.docLines = ['line one', 'line two']
this.rev = 3
this.ranges = { comments: [{ id: 123 }] }
this.newDoc = new this.DocModel({
name: this.docName,
lines: undefined,
_id: this.docId,
rev: 0,
})
this.ProjectEntityHandler.promises.getDoc.resolves({
lines: this.docLines,
rev: this.rev,
version: 'version',
ranges: this.ranges,
})
this.EditorController.promises.addDocWithRanges = sinon
.stub()
.resolves(this.newDoc)
await this.ProjectEntityRestoreHandler.promises.restoreDeletedDoc(
this.project._id,
this.docId,
docName,
this.user._id
)
const docNameMatcher = new RegExp(docName + '-\\d{4}-\\d{2}-\\d{2}-\\d+')
expect(
this.EditorController.promises.addDocWithRanges
).to.have.been.calledWith(
this.project._id,
null,
sinon.match(docNameMatcher),
this.docLines,
this.ranges,
null,
this.user._id
)
})
})

View File

@@ -0,0 +1,166 @@
import { vi } from 'vitest'
import sinon from 'sinon'
const modulePath =
'../../../../app/src/Features/Project/ProjectHistoryHandler.mjs'
describe('ProjectHistoryHandler', function () {
const projectId = '4eecb1c1bffa66588e0000a1'
beforeEach(async function (ctx) {
let Project
ctx.ProjectModel = Project = (function () {
Project = class Project {
static initClass() {
this.prototype.rootFolder = [this.rootFolder]
}
constructor(options) {
this._id = projectId
this.name = 'project_name_here'
this.rev = 0
}
}
Project.initClass()
return Project
})()
ctx.project = new ctx.ProjectModel()
ctx.historyId = ctx.project._id.toString()
ctx.callback = sinon.stub()
vi.doMock('@overleaf/settings', () => ({
default: (ctx.Settings = {}),
}))
vi.doMock('../../../../app/src/models/Project.js', () => ({
Project: ctx.ProjectModel,
}))
vi.doMock(
'../../../../app/src/Features/Project/ProjectDetailsHandler.js',
() => ({
default: (ctx.ProjectDetailsHandler = {
promises: {},
}),
})
)
vi.doMock('../../../../app/src/Features/History/HistoryManager.js', () => ({
default: (ctx.HistoryManager = {
promises: {},
}),
}))
vi.doMock(
'../../../../app/src/Features/Project/ProjectEntityUpdateHandler',
() => ({
default: (ctx.ProjectEntityUpdateHandler = {
promises: {},
}),
})
)
return (ctx.ProjectHistoryHandler = (await import(modulePath)).default)
})
describe('starting history for an existing project', function () {
beforeEach(async function (ctx) {
ctx.HistoryManager.promises.initializeProject = sinon
.stub()
.resolves(ctx.historyId)
ctx.HistoryManager.promises.flushProject = sinon.stub()
return (ctx.ProjectEntityUpdateHandler.promises.resyncProjectHistory =
sinon.stub())
})
describe('when the history does not already exist', function () {
beforeEach(async function (ctx) {
ctx.ProjectDetailsHandler.promises.getDetails = sinon
.stub()
.withArgs(projectId)
.resolves(ctx.project)
ctx.ProjectModel.updateOne = sinon.stub().resolves({ matchedCount: 1 })
return ctx.ProjectHistoryHandler.promises.ensureHistoryExistsForProject(
projectId
)
})
it('should get any existing history id for the project', async function (ctx) {
return ctx.ProjectDetailsHandler.promises.getDetails
.calledWith(projectId)
.should.equal(true)
})
it('should initialize a new history in the v1 history service', async function (ctx) {
return ctx.HistoryManager.promises.initializeProject.called.should.equal(
true
)
})
it('should set the new history id on the project', async function (ctx) {
return ctx.ProjectModel.updateOne
.calledWith(
{ _id: projectId, 'overleaf.history.id': { $exists: false } },
{ 'overleaf.history.id': ctx.historyId }
)
.should.equal(true)
})
it('should resync the project history', async function (ctx) {
return ctx.ProjectEntityUpdateHandler.promises.resyncProjectHistory
.calledWith(projectId)
.should.equal(true)
})
it('should flush the project history', async function (ctx) {
return ctx.HistoryManager.promises.flushProject
.calledWith(projectId)
.should.equal(true)
})
})
describe('when the history already exists', function () {
beforeEach(function (ctx) {
ctx.project.overleaf = { history: { id: 1234 } }
ctx.ProjectDetailsHandler.promises.getDetails = sinon
.stub()
.withArgs(projectId)
.resolves(ctx.project)
ctx.ProjectModel.updateOne = sinon.stub().resolves({ matchedCount: 1 })
return ctx.ProjectHistoryHandler.promises.ensureHistoryExistsForProject(
projectId
)
})
it('should get any existing history id for the project', async function (ctx) {
return ctx.ProjectDetailsHandler.promises.getDetails
.calledWith(projectId)
.should.equal(true)
})
it('should not initialize a new history in the v1 history service', async function (ctx) {
return ctx.HistoryManager.promises.initializeProject.called.should.equal(
false
)
})
it('should not set the new history id on the project', async function (ctx) {
return ctx.ProjectModel.updateOne.called.should.equal(false)
})
it('should not resync the project history', async function (ctx) {
return ctx.ProjectEntityUpdateHandler.promises.resyncProjectHistory.called.should.equal(
false
)
})
it('should not flush the project history', async function (ctx) {
return ctx.HistoryManager.promises.flushProject.called.should.equal(
false
)
})
})
})
})

View File

@@ -1,163 +0,0 @@
/* eslint-disable
max-len,
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS206: Consider reworking classes to avoid initClass
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { assert, expect } = require('chai')
const sinon = require('sinon')
const modulePath = '../../../../app/src/Features/Project/ProjectHistoryHandler'
const SandboxedModule = require('sandboxed-module')
describe('ProjectHistoryHandler', function () {
const projectId = '4eecb1c1bffa66588e0000a1'
const userId = 1234
beforeEach(function () {
let Project
this.ProjectModel = Project = (function () {
Project = class Project {
static initClass() {
this.prototype.rootFolder = [this.rootFolder]
}
constructor(options) {
this._id = projectId
this.name = 'project_name_here'
this.rev = 0
}
}
Project.initClass()
return Project
})()
this.project = new this.ProjectModel()
this.historyId = this.project._id.toString()
this.callback = sinon.stub()
return (this.ProjectHistoryHandler = SandboxedModule.require(modulePath, {
requires: {
'@overleaf/settings': (this.Settings = {}),
'../../models/Project': {
Project: this.ProjectModel,
},
'./ProjectDetailsHandler': (this.ProjectDetailsHandler = {
promises: {},
}),
'../History/HistoryManager': (this.HistoryManager = {
promises: {},
}),
'./ProjectEntityUpdateHandler': (this.ProjectEntityUpdateHandler = {
promises: {},
}),
},
}))
})
describe('starting history for an existing project', function () {
beforeEach(async function () {
this.HistoryManager.promises.initializeProject = sinon
.stub()
.resolves(this.historyId)
this.HistoryManager.promises.flushProject = sinon.stub()
return (this.ProjectEntityUpdateHandler.promises.resyncProjectHistory =
sinon.stub())
})
describe('when the history does not already exist', function () {
beforeEach(async function () {
this.ProjectDetailsHandler.promises.getDetails = sinon
.stub()
.withArgs(projectId)
.resolves(this.project)
this.ProjectModel.updateOne = sinon.stub().resolves({ matchedCount: 1 })
return this.ProjectHistoryHandler.promises.ensureHistoryExistsForProject(
projectId
)
})
it('should get any existing history id for the project', async function () {
return this.ProjectDetailsHandler.promises.getDetails
.calledWith(projectId)
.should.equal(true)
})
it('should initialize a new history in the v1 history service', async function () {
return this.HistoryManager.promises.initializeProject.called.should.equal(
true
)
})
it('should set the new history id on the project', async function () {
return this.ProjectModel.updateOne
.calledWith(
{ _id: projectId, 'overleaf.history.id': { $exists: false } },
{ 'overleaf.history.id': this.historyId }
)
.should.equal(true)
})
it('should resync the project history', async function () {
return this.ProjectEntityUpdateHandler.promises.resyncProjectHistory
.calledWith(projectId)
.should.equal(true)
})
it('should flush the project history', async function () {
return this.HistoryManager.promises.flushProject
.calledWith(projectId)
.should.equal(true)
})
})
describe('when the history already exists', function () {
beforeEach(function () {
this.project.overleaf = { history: { id: 1234 } }
this.ProjectDetailsHandler.promises.getDetails = sinon
.stub()
.withArgs(projectId)
.resolves(this.project)
this.ProjectModel.updateOne = sinon.stub().resolves({ matchedCount: 1 })
return this.ProjectHistoryHandler.promises.ensureHistoryExistsForProject(
projectId
)
})
it('should get any existing history id for the project', async function () {
return this.ProjectDetailsHandler.promises.getDetails
.calledWith(projectId)
.should.equal(true)
})
it('should not initialize a new history in the v1 history service', async function () {
return this.HistoryManager.promises.initializeProject.called.should.equal(
false
)
})
it('should not set the new history id on the project', async function () {
return this.ProjectModel.updateOne.called.should.equal(false)
})
it('should not resync the project history', async function () {
return this.ProjectEntityUpdateHandler.promises.resyncProjectHistory.called.should.equal(
false
)
})
it('should not flush the project history', async function () {
return this.HistoryManager.promises.flushProject.called.should.equal(
false
)
})
})
})
})

View File

@@ -0,0 +1,138 @@
import { vi } from 'vitest'
import sinon from 'sinon'
const modulePath = '../../../../app/src/Features/Referal/ReferalAllocator.mjs'
describe('ReferalAllocator', function () {
beforeEach(async function (ctx) {
vi.doMock('../../../../app/src/models/User.js', () => ({
User: (ctx.User = {}),
}))
vi.doMock(
'../../../../app/src/Features/Subscription/FeaturesUpdater.js',
() => ({
default: (ctx.FeaturesUpdater = {}),
})
)
vi.doMock('@overleaf/settings', () => ({
default: (ctx.Settings = {}),
}))
ctx.ReferalAllocator = (await import(modulePath)).default
ctx.referal_id = 'referal-id-123'
ctx.referal_medium = 'twitter'
ctx.user_id = 'user-id-123'
ctx.new_user_id = 'new-user-id-123'
ctx.FeaturesUpdater.promises = {
refreshFeatures: sinon.stub().resolves(),
}
ctx.User.updateOne = sinon.stub().returns({
exec: sinon.stub().resolves(),
})
ctx.User.findOne = sinon.stub().returns({
exec: sinon.stub().resolves({ _id: ctx.user_id }),
})
})
describe('allocate', function () {
describe('when the referal was a bonus referal', function () {
beforeEach(async function (ctx) {
ctx.referal_source = 'bonus'
await ctx.ReferalAllocator.promises.allocate(
ctx.referal_id,
ctx.new_user_id,
ctx.referal_source,
ctx.referal_medium
)
})
it('should update the referring user with the refered users id', function (ctx) {
ctx.User.updateOne
.calledWith(
{
referal_id: ctx.referal_id,
},
{
$push: {
refered_users: ctx.new_user_id,
},
$inc: {
refered_user_count: 1,
},
}
)
.should.equal(true)
})
it('find the referring users id', function (ctx) {
ctx.User.findOne
.calledWith({ referal_id: ctx.referal_id })
.should.equal(true)
})
it("should refresh the user's subscription", function (ctx) {
ctx.FeaturesUpdater.promises.refreshFeatures
.calledWith(ctx.user_id)
.should.equal(true)
})
})
describe('when there is no user for the referal id', function () {
beforeEach(async function (ctx) {
ctx.referal_source = 'bonus'
ctx.referal_id = 'wombat'
ctx.User.findOne = sinon.stub().returns({
exec: sinon.stub().resolves(null),
})
await ctx.ReferalAllocator.promises.allocate(
ctx.referal_id,
ctx.new_user_id,
ctx.referal_source,
ctx.referal_medium
)
})
it('should find the referring users id', function (ctx) {
ctx.User.findOne
.calledWith({ referal_id: ctx.referal_id })
.should.equal(true)
})
it('should not update the referring user with the refered users id', function (ctx) {
ctx.User.updateOne.called.should.equal(false)
})
it('should not assign the user a bonus', function (ctx) {
ctx.FeaturesUpdater.promises.refreshFeatures.called.should.equal(false)
})
})
describe('when the referal is not a bonus referal', function () {
beforeEach(async function (ctx) {
ctx.referal_source = 'public_share'
await ctx.ReferalAllocator.promises.allocate(
ctx.referal_id,
ctx.new_user_id,
ctx.referal_source,
ctx.referal_medium
)
})
it('should not update the referring user with the refered users id', function (ctx) {
ctx.User.updateOne.called.should.equal(false)
})
it('find the referring users id', function (ctx) {
ctx.User.findOne
.calledWith({ referal_id: ctx.referal_id })
.should.equal(true)
})
it('should not assign the user a bonus', function (ctx) {
ctx.FeaturesUpdater.promises.refreshFeatures.called.should.equal(false)
})
})
})
})

View File

@@ -1,133 +0,0 @@
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const modulePath = require('path').join(
__dirname,
'../../../../app/src/Features/Referal/ReferalAllocator.js'
)
describe('ReferalAllocator', function () {
beforeEach(function () {
this.ReferalAllocator = SandboxedModule.require(modulePath, {
requires: {
'../../models/User': {
User: (this.User = {}),
},
'../Subscription/FeaturesUpdater': (this.FeaturesUpdater = {}),
'@overleaf/settings': (this.Settings = {}),
},
})
this.referal_id = 'referal-id-123'
this.referal_medium = 'twitter'
this.user_id = 'user-id-123'
this.new_user_id = 'new-user-id-123'
this.FeaturesUpdater.promises = {
refreshFeatures: sinon.stub().resolves(),
}
this.User.updateOne = sinon.stub().returns({
exec: sinon.stub().resolves(),
})
this.User.findOne = sinon.stub().returns({
exec: sinon.stub().resolves({ _id: this.user_id }),
})
})
describe('allocate', function () {
describe('when the referal was a bonus referal', function () {
beforeEach(async function () {
this.referal_source = 'bonus'
await this.ReferalAllocator.promises.allocate(
this.referal_id,
this.new_user_id,
this.referal_source,
this.referal_medium
)
})
it('should update the referring user with the refered users id', function () {
this.User.updateOne
.calledWith(
{
referal_id: this.referal_id,
},
{
$push: {
refered_users: this.new_user_id,
},
$inc: {
refered_user_count: 1,
},
}
)
.should.equal(true)
})
it('find the referring users id', function () {
this.User.findOne
.calledWith({ referal_id: this.referal_id })
.should.equal(true)
})
it("should refresh the user's subscription", function () {
this.FeaturesUpdater.promises.refreshFeatures
.calledWith(this.user_id)
.should.equal(true)
})
})
describe('when there is no user for the referal id', function () {
beforeEach(async function () {
this.referal_source = 'bonus'
this.referal_id = 'wombat'
this.User.findOne = sinon.stub().returns({
exec: sinon.stub().resolves(null),
})
await this.ReferalAllocator.promises.allocate(
this.referal_id,
this.new_user_id,
this.referal_source,
this.referal_medium
)
})
it('should find the referring users id', function () {
this.User.findOne
.calledWith({ referal_id: this.referal_id })
.should.equal(true)
})
it('should not update the referring user with the refered users id', function () {
this.User.updateOne.called.should.equal(false)
})
it('should not assign the user a bonus', function () {
this.FeaturesUpdater.promises.refreshFeatures.called.should.equal(false)
})
})
describe('when the referal is not a bonus referal', function () {
beforeEach(async function () {
this.referal_source = 'public_share'
await this.ReferalAllocator.promises.allocate(
this.referal_id,
this.new_user_id,
this.referal_source,
this.referal_medium
)
})
it('should not update the referring user with the refered users id', function () {
this.User.updateOne.called.should.equal(false)
})
it('find the referring users id', function () {
this.User.findOne
.calledWith({ referal_id: this.referal_id })
.should.equal(true)
})
it('should not assign the user a bonus', function () {
this.FeaturesUpdater.promises.refreshFeatures.called.should.equal(false)
})
})
})
})

View File

@@ -0,0 +1,89 @@
import { vi } from 'vitest'
import sinon from 'sinon'
import MockResponse from '../helpers/MockResponse.js'
import MockRequest from '../helpers/MockRequest.js'
const modulePath = '../../../../app/src/Features/SplitTests/SplitTestMiddleware'
describe('SplitTestMiddleware', function () {
beforeEach(async function (ctx) {
vi.doMock(
'../../../../app/src/Features/SplitTests/SplitTestHandler.js',
() => ({
default: (ctx.SplitTestHandler = {
promises: {
getAssignment: sinon.stub().resolves(),
},
}),
})
)
ctx.SplitTestMiddleware = (await import(modulePath)).default
ctx.req = new MockRequest()
ctx.res = new MockResponse()
ctx.next = sinon.stub()
})
it('assign multiple split test variants in locals', async function (ctx) {
ctx.SplitTestHandler.promises.getAssignment
.withArgs(ctx.req, 'ui-overhaul')
.resolves({
variant: 'default',
})
ctx.SplitTestHandler.promises.getAssignment
.withArgs(ctx.req, 'other-test')
.resolves({
variant: 'foobar',
})
const middleware = ctx.SplitTestMiddleware.loadAssignmentsInLocals([
'ui-overhaul',
'other-test',
])
await middleware(ctx.req, ctx.res, ctx.next)
sinon.assert.calledWith(
ctx.SplitTestHandler.promises.getAssignment,
ctx.req,
ctx.res,
'ui-overhaul'
)
sinon.assert.calledWith(
ctx.SplitTestHandler.promises.getAssignment,
ctx.req,
ctx.res,
'other-test'
)
sinon.assert.calledOnce(ctx.next)
})
it('assign no split test variant in locals', async function (ctx) {
const middleware = ctx.SplitTestMiddleware.loadAssignmentsInLocals([])
await middleware(ctx.req, ctx.res, ctx.next)
sinon.assert.notCalled(ctx.SplitTestHandler.promises.getAssignment)
sinon.assert.calledOnce(ctx.next)
})
it('exception thrown by assignment does not fail the request', async function (ctx) {
ctx.SplitTestHandler.promises.getAssignment
.withArgs(ctx.req, ctx.res, 'some-test')
.throws(new Error('failure'))
const middleware = ctx.SplitTestMiddleware.loadAssignmentsInLocals([
'some-test',
])
await middleware(ctx.req, ctx.res, ctx.next)
sinon.assert.calledWith(
ctx.SplitTestHandler.promises.getAssignment,
ctx.req,
ctx.res,
'some-test'
)
sinon.assert.calledOnce(ctx.next)
})
})

View File

@@ -1,89 +0,0 @@
const SandboxedModule = require('sandboxed-module')
const path = require('path')
const modulePath = path.join(
__dirname,
'../../../../app/src/Features/SplitTests/SplitTestMiddleware'
)
const sinon = require('sinon')
const MockResponse = require('../helpers/MockResponse')
const MockRequest = require('../helpers/MockRequest')
describe('SplitTestMiddleware', function () {
beforeEach(function () {
this.SplitTestMiddleware = SandboxedModule.require(modulePath, {
requires: {
'./SplitTestHandler': (this.SplitTestHandler = {
promises: {
getAssignment: sinon.stub().resolves(),
},
}),
},
})
this.req = new MockRequest()
this.res = new MockResponse()
this.next = sinon.stub()
})
it('assign multiple split test variants in locals', async function () {
this.SplitTestHandler.promises.getAssignment
.withArgs(this.req, 'ui-overhaul')
.resolves({
variant: 'default',
})
this.SplitTestHandler.promises.getAssignment
.withArgs(this.req, 'other-test')
.resolves({
variant: 'foobar',
})
const middleware = this.SplitTestMiddleware.loadAssignmentsInLocals([
'ui-overhaul',
'other-test',
])
await middleware(this.req, this.res, this.next)
sinon.assert.calledWith(
this.SplitTestHandler.promises.getAssignment,
this.req,
this.res,
'ui-overhaul'
)
sinon.assert.calledWith(
this.SplitTestHandler.promises.getAssignment,
this.req,
this.res,
'other-test'
)
sinon.assert.calledOnce(this.next)
})
it('assign no split test variant in locals', async function () {
const middleware = this.SplitTestMiddleware.loadAssignmentsInLocals([])
await middleware(this.req, this.res, this.next)
sinon.assert.notCalled(this.SplitTestHandler.promises.getAssignment)
sinon.assert.calledOnce(this.next)
})
it('exception thrown by assignment does not fail the request', async function () {
this.SplitTestHandler.promises.getAssignment
.withArgs(this.req, this.res, 'some-test')
.throws(new Error('failure'))
const middleware = this.SplitTestMiddleware.loadAssignmentsInLocals([
'some-test',
])
await middleware(this.req, this.res, this.next)
sinon.assert.calledWith(
this.SplitTestHandler.promises.getAssignment,
this.req,
this.res,
'some-test'
)
sinon.assert.calledOnce(this.next)
})
})