mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
[web] enable mongo notablescan in CI (#29501)
* [monorepo] record ERROR/FATAL log messages in junit report * [web] put SaaS specific code behind feature flag * [web] use split test cache for getting user assignments The unit tests needed updating as they did not replicate any of the mongo filtering. The acceptance tests cover this logic. * [web] make better use of existing indexes * [web] avoid col-scan in tests of notifications module * [web] remove cleanup of empty feedbacks collection * [web] add assertion for reason of rejected request in launchpad test * [web] add missing indexes * [web] enable mongo notablescan * [web] make emailNotifications tests compatible with notablescan GitOrigin-RevId: b888f2feeb3a0e915f068ae1c4ea23ec17821221
This commit is contained in:
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
20
libraries/logger/test-log-recorder.js
Normal file
20
libraries/logger/test-log-recorder.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const logger = require('./')
|
||||
const bunyan = require('bunyan')
|
||||
const serializers = require('./serializers')
|
||||
|
||||
function testLogRecorder() {
|
||||
const currentTest = this.currentTest
|
||||
for (const level of ['error', 'fatal']) {
|
||||
logger[level] = (info, msg) => {
|
||||
const entry = { level, ...info, msg }
|
||||
for (const [name, fn] of Object.entries(serializers)) {
|
||||
if (name in entry) entry[name] = fn(entry[name])
|
||||
}
|
||||
currentTest.consoleErrors = (currentTest.consoleErrors || []).concat(
|
||||
JSON.stringify(info, bunyan.safeCycles())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = testLogRecorder
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createServer } from '../../../../app/js/server.js'
|
||||
import { promisify } from 'node:util'
|
||||
import './MongoHelper.js'
|
||||
import testLogRecorder from '@overleaf/logger/test-log-recorder.js'
|
||||
|
||||
export { db } from '../../../../app/js/mongodb.js'
|
||||
|
||||
@@ -14,3 +15,7 @@ export async function ensureRunning() {
|
||||
}
|
||||
return serverPromise
|
||||
}
|
||||
|
||||
if (process.env.CI === 'true') {
|
||||
beforeEach('record error logs in junit', testLogRecorder)
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ const fetch = require('node-fetch')
|
||||
const { knex, redis } = require('../storage')
|
||||
const { exec } = require('node:child_process')
|
||||
const { promisify } = require('node:util')
|
||||
const testLogRecorder = require('@overleaf/logger/test-log-recorder')
|
||||
|
||||
// ensure every ObjectId has the id string as a property for correct comparisons
|
||||
require('mongodb').ObjectId.cacheHexString = true
|
||||
@@ -56,5 +57,6 @@ module.exports = {
|
||||
mochaHooks: {
|
||||
beforeAll: [setupPostgresDatabase, setupMongoDatabase, createGcsBuckets],
|
||||
afterAll: [tearDownConnectionPool],
|
||||
beforeEach: process.env.CI === 'true' ? [testLogRecorder] : [],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ module.exports = {
|
||||
mochaFile: `reports/junit-mocha-${process.env.MOCHA_GREP}.xml`,
|
||||
includePending: true,
|
||||
jenkinsMode: true,
|
||||
output: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import PlansLocator from '../Subscription/PlansLocator.mjs'
|
||||
import Settings from '@overleaf/settings'
|
||||
import InstitutionsGetter from './InstitutionsGetter.mjs'
|
||||
import FeaturesHelper from '../Subscription/FeaturesHelper.mjs'
|
||||
import Features from '../../infrastructure/Features.mjs'
|
||||
|
||||
async function _getInstitutionsAddons(userId) {
|
||||
if (!Features.hasFeature('saas')) return {}
|
||||
const affiliates =
|
||||
await InstitutionsGetter.promises.getCurrentAffiliations(userId)
|
||||
// currently only addOn available to institutions is assist/WF bundle,
|
||||
@@ -17,6 +19,7 @@ async function _getInstitutionsAddons(userId) {
|
||||
}
|
||||
|
||||
async function getInstitutionsFeatures(userId) {
|
||||
if (!Features.hasFeature('saas')) return {}
|
||||
const planCode = await getInstitutionsPlan(userId)
|
||||
const plan = planCode && PlansLocator.findLocalPlanInSettings(planCode)
|
||||
let features = plan && plan.features
|
||||
@@ -28,6 +31,7 @@ async function getInstitutionsFeatures(userId) {
|
||||
}
|
||||
|
||||
async function getInstitutionsPlan(userId) {
|
||||
if (!Features.hasFeature('saas')) return null
|
||||
if (await hasLicence(userId)) {
|
||||
return Settings.institutionPlanCode
|
||||
}
|
||||
@@ -35,6 +39,7 @@ async function getInstitutionsPlan(userId) {
|
||||
}
|
||||
|
||||
async function hasLicence(userId) {
|
||||
if (!Features.hasFeature('saas')) return false
|
||||
const emailsData = await UserGetter.promises.getUserFullEmails(userId)
|
||||
return emailsData.some(emailData => emailData.emailHasInstitutionLicence)
|
||||
}
|
||||
|
||||
@@ -232,12 +232,11 @@ async function getActiveAssignmentsForUser(
|
||||
return {}
|
||||
}
|
||||
|
||||
const splitTests = await SplitTest.find({
|
||||
$where: 'this.versions[this.versions.length - 1].active',
|
||||
...(removeArchived && { archived: { $ne: true } }),
|
||||
}).exec()
|
||||
const splitTests = (await SplitTestCache.get('')).values()
|
||||
const assignments = {}
|
||||
for (const splitTest of splitTests) {
|
||||
if (!splitTest.versions[splitTest.versions.length - 1].active) continue
|
||||
if (removeArchived && splitTest.archived) continue
|
||||
const { activeForUser, selectedVariantName, phase, versionNumber } =
|
||||
await _getAssignmentMetadata(user.analyticsId, user, splitTest)
|
||||
if (activeForUser) {
|
||||
|
||||
@@ -10,9 +10,11 @@ import { DeletedSubscription } from '../../models/DeletedSubscription.mjs'
|
||||
import logger from '@overleaf/logger'
|
||||
import { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } from './AiHelper.mjs'
|
||||
import './GroupPlansData.mjs' // make sure dynamic group plans are loaded
|
||||
import Features from '../../infrastructure/Features.mjs'
|
||||
|
||||
const SubscriptionLocator = {
|
||||
async getUsersSubscription(userOrId) {
|
||||
if (!Features.hasFeature('saas')) return undefined
|
||||
const userId = SubscriptionLocator._getUserId(userOrId)
|
||||
const subscription = await Subscription.findOne({ admin_id: userId }).exec()
|
||||
logger.debug({ userId }, 'got users subscription')
|
||||
@@ -25,6 +27,7 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async getUserIndividualSubscription(userOrId) {
|
||||
if (!Features.hasFeature('saas')) return undefined
|
||||
const userId = SubscriptionLocator._getUserId(userOrId)
|
||||
const subscription = await Subscription.findOne({
|
||||
admin_id: userId,
|
||||
@@ -35,6 +38,7 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async getManagedGroupSubscriptions(userOrId) {
|
||||
if (!Features.hasFeature('saas')) return []
|
||||
return await Subscription.find({
|
||||
manager_ids: userOrId,
|
||||
groupPlan: true,
|
||||
@@ -44,6 +48,7 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async getMemberSubscriptions(userOrId, populate = []) {
|
||||
if (!Features.hasFeature('saas')) return []
|
||||
const userId = SubscriptionLocator._getUserId(userOrId)
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return await Subscription.find({ member_ids: userId })
|
||||
@@ -53,6 +58,7 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async getAdminEmail(subscriptionId) {
|
||||
if (!Features.hasFeature('saas')) return undefined
|
||||
const subscription = await Subscription.findById(subscriptionId)
|
||||
.populate('admin_id', 'email')
|
||||
.exec()
|
||||
@@ -61,6 +67,7 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async getAdminEmailAndName(subscriptionId) {
|
||||
if (!Features.hasFeature('saas')) return undefined
|
||||
const subscription = await Subscription.findById(subscriptionId)
|
||||
.populate('admin_id', ['email', 'first_name', 'last_name'])
|
||||
.exec()
|
||||
@@ -69,6 +76,7 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async hasRecurlyGroupSubscription(userOrId) {
|
||||
if (!Features.hasFeature('saas')) return false
|
||||
const userId = SubscriptionLocator._getUserId(userOrId)
|
||||
return await Subscription.exists({
|
||||
groupPlan: true,
|
||||
@@ -93,6 +101,7 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async getGroupSubscriptionsMemberOf(userId) {
|
||||
if (!Features.hasFeature('saas')) return []
|
||||
return await Subscription.find(
|
||||
{ member_ids: userId },
|
||||
{ _id: 1, planCode: 1, userFeaturesDisabled: 1 }
|
||||
@@ -100,6 +109,7 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async getUniqueManagedSubscriptionMemberOf(userId) {
|
||||
if (!Features.hasFeature('saas')) return null
|
||||
return await Subscription.findOne(
|
||||
{ member_ids: userId, managedUsersEnabled: true },
|
||||
{ _id: 1 }
|
||||
@@ -125,8 +135,13 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async getGroupsWithTeamInvitesEmail(email) {
|
||||
if (!Features.hasFeature('saas')) return []
|
||||
return await Subscription.find(
|
||||
{ teamInvites: { $elemMatch: { email } } },
|
||||
{
|
||||
teamInvites: { $elemMatch: { email } },
|
||||
// Add partialFilterExpression from index.
|
||||
'teamInvites.email': { $exists: true },
|
||||
},
|
||||
{ teamInvites: 1 }
|
||||
).exec()
|
||||
},
|
||||
@@ -136,6 +151,7 @@ const SubscriptionLocator = {
|
||||
},
|
||||
|
||||
async getUserDeletedSubscriptions(userId) {
|
||||
if (!Features.hasFeature('saas')) return []
|
||||
return await DeletedSubscription.find({
|
||||
'subscription.admin_id': userId,
|
||||
}).exec()
|
||||
|
||||
@@ -3,6 +3,7 @@ import logger from '@overleaf/logger'
|
||||
import { UserAuditLogEntry } from '../../models/UserAuditLogEntry.mjs'
|
||||
import { callbackify } from 'node:util'
|
||||
import SubscriptionLocator from '../Subscription/SubscriptionLocator.mjs'
|
||||
import Features from '../../infrastructure/Features.mjs'
|
||||
|
||||
function _canHaveNoIpAddressId(operation, info) {
|
||||
if (operation === 'add-email' && info.script) return true
|
||||
@@ -87,7 +88,10 @@ async function addEntry(userId, operation, initiatorId, ipAddress, info = {}) {
|
||||
ipAddress,
|
||||
}
|
||||
|
||||
if (MANAGED_GROUP_USER_EVENTS.includes(operation)) {
|
||||
if (
|
||||
MANAGED_GROUP_USER_EVENTS.includes(operation) &&
|
||||
Features.hasFeature('saas')
|
||||
) {
|
||||
try {
|
||||
const managedSubscription =
|
||||
await SubscriptionLocator.promises.getUniqueManagedSubscriptionMemberOf(
|
||||
|
||||
@@ -4,7 +4,6 @@ import Settings from '@overleaf/settings'
|
||||
import { User } from '../../models/User.mjs'
|
||||
import { DeletedUser } from '../../models/DeletedUser.mjs'
|
||||
import { UserAuditLogEntry } from '../../models/UserAuditLogEntry.mjs'
|
||||
import { Feedback } from '../../models/Feedback.mjs'
|
||||
import NewsletterManager from '../Newsletter/NewsletterManager.mjs'
|
||||
import ProjectDeleter from '../Project/ProjectDeleter.mjs'
|
||||
import SubscriptionHandler from '../Subscription/SubscriptionHandler.mjs'
|
||||
@@ -18,6 +17,7 @@ import Modules from '../../infrastructure/Modules.mjs'
|
||||
import Errors from '../Errors/Errors.js'
|
||||
import OnboardingDataCollectionManager from '../OnboardingDataCollection/OnboardingDataCollectionManager.mjs'
|
||||
import EmailHandler from '../Email/EmailHandler.mjs'
|
||||
import Features from '../../infrastructure/Features.mjs'
|
||||
|
||||
export default {
|
||||
deleteUser: callbackify(deleteUser),
|
||||
@@ -96,8 +96,6 @@ async function expireDeletedUser(userId) {
|
||||
try {
|
||||
logger.info({ userId }, 'firing expireDeletedUser hook')
|
||||
await Modules.promises.hooks.fire('expireDeletedUser', userId)
|
||||
logger.info({ userId }, 'removing deleted user feedback records')
|
||||
await Feedback.deleteMany({ userId }).exec()
|
||||
logger.info({ userId }, 'removing deleted user onboarding data')
|
||||
await OnboardingDataCollectionManager.deleteOnboardingDataCollection(userId)
|
||||
logger.info({ userId }, 'redacting PII from the deleted user record')
|
||||
@@ -157,6 +155,7 @@ async function expireDeletedUsersAfterDuration() {
|
||||
}
|
||||
|
||||
async function ensureCanDeleteUser(user) {
|
||||
if (!Features.hasFeature('saas')) return
|
||||
const subscription =
|
||||
await SubscriptionLocator.promises.getUsersSubscription(user)
|
||||
if (subscription) {
|
||||
@@ -212,16 +211,18 @@ async function _cleanupUser(user) {
|
||||
|
||||
logger.info({ userId }, '[cleanupUser] removing user sessions from Redis')
|
||||
await UserSessionsManager.promises.removeSessionsFromRedis(user)
|
||||
logger.info({ userId }, '[cleanupUser] unsubscribing from newsletters')
|
||||
await NewsletterManager.promises.unsubscribe(user, { delete: true })
|
||||
logger.info({ userId }, '[cleanupUser] cancelling subscription')
|
||||
await SubscriptionHandler.promises.cancelSubscription(user)
|
||||
logger.info({ userId }, '[cleanupUser] deleting affiliations')
|
||||
await InstitutionsAPI.promises.deleteAffiliations(userId)
|
||||
logger.info({ userId }, '[cleanupUser] removing user from groups')
|
||||
await SubscriptionUpdater.promises.removeUserFromAllGroups(userId)
|
||||
logger.info({ userId }, '[cleanupUser] removing user from memberships')
|
||||
await UserMembershipsHandler.promises.removeUserFromAllEntities(userId)
|
||||
if (Features.hasFeature('saas')) {
|
||||
logger.info({ userId }, '[cleanupUser] unsubscribing from newsletters')
|
||||
await NewsletterManager.promises.unsubscribe(user, { delete: true })
|
||||
logger.info({ userId }, '[cleanupUser] cancelling subscription')
|
||||
await SubscriptionHandler.promises.cancelSubscription(user)
|
||||
logger.info({ userId }, '[cleanupUser] deleting affiliations')
|
||||
await InstitutionsAPI.promises.deleteAffiliations(userId)
|
||||
logger.info({ userId }, '[cleanupUser] removing user from groups')
|
||||
await SubscriptionUpdater.promises.removeUserFromAllGroups(userId)
|
||||
logger.info({ userId }, '[cleanupUser] removing user from memberships')
|
||||
await UserMembershipsHandler.promises.removeUserFromAllEntities(userId)
|
||||
}
|
||||
logger.info({ userId }, '[cleanupUser] removing personal access tokens')
|
||||
await Modules.promises.hooks.fire('cleanupPersonalAccessTokens', userId, [
|
||||
'collabratec',
|
||||
|
||||
@@ -16,6 +16,7 @@ import UserMembershipEntityConfigs from './UserMembershipEntityConfigs.mjs'
|
||||
import * as InstitutionModel from '../../models/Institution.mjs'
|
||||
import * as SubscriptionModel from '../../models/Subscription.mjs'
|
||||
import * as PublisherModel from '../../models/Publisher.mjs'
|
||||
import Features from '../../infrastructure/Features.mjs'
|
||||
|
||||
const EntityModels = {
|
||||
Institution: InstitutionModel.Institution,
|
||||
@@ -66,6 +67,7 @@ const UserMembershipsHandler = {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
if (!Features.hasFeature('saas')) return callback(null, [])
|
||||
const query = Object.assign({}, entityConfig.baseQuery)
|
||||
query[entityConfig.fields.access] = userId
|
||||
EntityModels[entityConfig.modelName]
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import _ from 'lodash'
|
||||
import Settings from '@overleaf/settings'
|
||||
|
||||
const supportModuleAvailable = Settings.moduleImportSequence.includes('support')
|
||||
const supportModuleAvailable =
|
||||
Settings.moduleImportSequence?.includes('support')
|
||||
|
||||
const symbolPaletteModuleAvailable =
|
||||
Settings.moduleImportSequence.includes('symbol-palette')
|
||||
Settings.moduleImportSequence?.includes('symbol-palette')
|
||||
|
||||
const trackChangesModuleAvailable =
|
||||
Settings.moduleImportSequence.includes('track-changes')
|
||||
Settings.moduleImportSequence?.includes('track-changes')
|
||||
|
||||
/**
|
||||
* @typedef {Object} Settings
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import mongoose from '../infrastructure/Mongoose.mjs'
|
||||
const { Schema } = mongoose
|
||||
const { ObjectId } = Schema
|
||||
|
||||
export const FeedbackSchema = new Schema(
|
||||
{
|
||||
userId: {
|
||||
type: ObjectId,
|
||||
ref: 'User',
|
||||
},
|
||||
source: String,
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default() {
|
||||
return new Date()
|
||||
},
|
||||
},
|
||||
data: {},
|
||||
},
|
||||
{ minimize: false }
|
||||
)
|
||||
|
||||
export const Feedback = mongoose.model('Feedback', FeedbackSchema)
|
||||
@@ -130,7 +130,7 @@ services:
|
||||
|
||||
mongo:
|
||||
image: mongo:8.0.11
|
||||
command: --replSet overleaf
|
||||
command: --replSet overleaf --notablescan
|
||||
volumes:
|
||||
- ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
|
||||
- ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
|
||||
|
||||
@@ -124,7 +124,7 @@ services:
|
||||
|
||||
mongo:
|
||||
image: mongo:8.0.11
|
||||
command: --replSet overleaf
|
||||
command: --replSet overleaf --notablescan
|
||||
volumes:
|
||||
- ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
|
||||
- ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
|
||||
|
||||
@@ -67,6 +67,9 @@ describe('Launchpad', function () {
|
||||
}),
|
||||
})
|
||||
expect(badPostResponse.status).to.equal(403)
|
||||
expect(await badPostResponse.json()).to.deep.equal({
|
||||
message: { type: 'error', text: 'admin user already exists' },
|
||||
})
|
||||
|
||||
// Log in as this new admin user
|
||||
const adminUser = await UserHelper.loginUser({
|
||||
|
||||
@@ -45,12 +45,16 @@ describe('Deleting a user', function () {
|
||||
'user',
|
||||
'login',
|
||||
(results, cb) => {
|
||||
const subscription = new Subscription({
|
||||
admin_id: results.user._id,
|
||||
})
|
||||
subscription.ensureExists(err => {
|
||||
cb(err, subscription)
|
||||
})
|
||||
if (Features.hasFeature('saas')) {
|
||||
const subscription = new Subscription({
|
||||
admin_id: results.user._id,
|
||||
})
|
||||
subscription.ensureExists(err => {
|
||||
cb(err, subscription)
|
||||
})
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -88,7 +92,7 @@ describe('Deleting a user', function () {
|
||||
this.user.deleteUser(error => {
|
||||
expect(error).not.to.exist
|
||||
db.deletedUsers.findOne(
|
||||
{ 'user._id': user._id },
|
||||
{ 'user.email': user.email },
|
||||
(error, deletedUser) => {
|
||||
expect(error).not.to.exist
|
||||
expect(deletedUser).to.exist
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect } from 'chai'
|
||||
import { User } from '../../../app/src/models/User.mjs'
|
||||
import { Subscription } from '../../../app/src/models/Subscription.mjs'
|
||||
import Features from '../../../app/src/infrastructure/Features.mjs'
|
||||
|
||||
describe('mongoose', function () {
|
||||
describe('User', function () {
|
||||
@@ -50,6 +51,10 @@ describe('mongoose', function () {
|
||||
let user
|
||||
|
||||
beforeEach(async function () {
|
||||
if (!Features.hasFeature('saas')) {
|
||||
this.skip()
|
||||
}
|
||||
|
||||
user = await User.create({ email: 'wombat@potato.net' })
|
||||
})
|
||||
|
||||
|
||||
@@ -163,9 +163,10 @@ describe('MongoTests', function () {
|
||||
})
|
||||
|
||||
describe('with an object as query', function () {
|
||||
const signUpDate = new Date('2025-11-03T16:38:17.320Z')
|
||||
beforeEach(async function addHiddenFlag() {
|
||||
// add a mongo field that does not exist on the other users
|
||||
await ghost.mongoUpdate({ $set: { hidden: 1 } })
|
||||
await ghost.mongoUpdate({ $set: { signUpDate } })
|
||||
})
|
||||
|
||||
it('should pass through the query', function () {
|
||||
@@ -176,7 +177,7 @@ describe('MongoTests', function () {
|
||||
|
||||
describe('when searching for hidden users', function () {
|
||||
it('should match the ghost only', async function () {
|
||||
const query = normalizeMultiQuery({ hidden: 1 })
|
||||
const query = normalizeMultiQuery({ signUpDate })
|
||||
|
||||
const users = await db.users.find(query).toArray()
|
||||
expect(users).to.have.length(1)
|
||||
@@ -186,7 +187,7 @@ describe('MongoTests', function () {
|
||||
|
||||
describe('when searching for non hidden users', function () {
|
||||
it('should find the three users', async function () {
|
||||
const query = normalizeMultiQuery({ hidden: { $exists: false } })
|
||||
const query = normalizeMultiQuery({ signUpDate: { $ne: signUpDate } })
|
||||
|
||||
await expectToFindTheThreeUsers(query)
|
||||
})
|
||||
|
||||
@@ -265,6 +265,10 @@ describe('PrimaryEmailCheck', function () {
|
||||
|
||||
describe('when the user is a managed user', function () {
|
||||
beforeEach(async function () {
|
||||
if (!Features.hasFeature('saas')) {
|
||||
this.skip()
|
||||
}
|
||||
|
||||
const adminUser = await UserHelper.createUser()
|
||||
this.subscription = new Subscription({
|
||||
adminId: adminUser._id,
|
||||
|
||||
@@ -6,15 +6,24 @@ import Subscription from './helpers/Subscription.mjs'
|
||||
import Publisher from './helpers/Publisher.mjs'
|
||||
import sinon from 'sinon'
|
||||
import RecurlyClient from '../../../app/src/Features/Subscription/RecurlyClient.mjs'
|
||||
import Features from '../../../app/src/infrastructure/Features.mjs'
|
||||
|
||||
describe('UserMembershipAuthorization', function () {
|
||||
beforeEach(function (done) {
|
||||
if (!Features.hasFeature('saas')) {
|
||||
this.skip()
|
||||
}
|
||||
|
||||
this.user = new User()
|
||||
sinon.stub(RecurlyClient.promises, 'getSubscription').resolves({})
|
||||
async.series([this.user.ensureUserExists.bind(this.user)], done)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
if (!Features.hasFeature('saas')) {
|
||||
return
|
||||
}
|
||||
|
||||
RecurlyClient.promises.getSubscription.restore()
|
||||
})
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { injectRouteAfter } from './injectRoute.mjs'
|
||||
import SplitTestHandler from '../../../../app/src/Features/SplitTests/SplitTestHandler.mjs'
|
||||
import SplitTestSessionHandler from '../../../../app/src/Features/SplitTests/SplitTestSessionHandler.mjs'
|
||||
import Modules from '../../../../app/src/infrastructure/Modules.mjs'
|
||||
import testLogRecorder from '@overleaf/logger/test-log-recorder.js'
|
||||
|
||||
const app = Server.app
|
||||
|
||||
@@ -114,3 +115,7 @@ after('stop main app', async function () {
|
||||
Settings.gracefulShutdownDelayInMs = 1
|
||||
await gracefulShutdown(server, 'tests')
|
||||
})
|
||||
|
||||
if (process.env.CI === 'true') {
|
||||
beforeEach('record error logs in junit', testLogRecorder)
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@ module.exports = {
|
||||
jenkinsMode: true,
|
||||
jenkinsClassnamePrefix: process.env.MOCHA_ROOT_SUITE_NAME,
|
||||
rootSuiteTitle: process.env.MOCHA_ROOT_SUITE_NAME,
|
||||
outputs: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ describe('InstitutionsFeatures', function () {
|
||||
vi.doMock('@overleaf/settings', () => ({
|
||||
default: {
|
||||
institutionPlanCode: ctx.institutionPlanCode,
|
||||
overleaf: {},
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
@@ -301,11 +301,6 @@ describe('SplitTestHandler', function () {
|
||||
variantName: 'variant-1',
|
||||
versionNumber: 2,
|
||||
},
|
||||
'not-active-test': {
|
||||
phase: 'release',
|
||||
variantName: 'variant-1',
|
||||
versionNumber: 1,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -99,10 +99,6 @@ describe('UserDeleter', function () {
|
||||
promises: { hooks: { fire: sinon.stub().resolves() } },
|
||||
}
|
||||
|
||||
ctx.Feedback = {
|
||||
deleteMany: sinon.stub().returns({ exec: sinon.stub().resolves() }),
|
||||
}
|
||||
|
||||
ctx.OnboardingDataCollectionManager = {
|
||||
deleteOnboardingDataCollection: sinon.stub().resolves(),
|
||||
}
|
||||
@@ -127,10 +123,6 @@ describe('UserDeleter', function () {
|
||||
DeletedUser,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/models/Feedback', () => ({
|
||||
Feedback: ctx.Feedback,
|
||||
}))
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Newsletter/NewsletterManager',
|
||||
() => ({
|
||||
@@ -788,13 +780,6 @@ describe('UserDeleter', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should delete Feeback', async function (ctx) {
|
||||
await ctx.UserDeleter.promises.expireDeletedUser(ctx.userId)
|
||||
expect(ctx.Feedback.deleteMany).to.have.been.calledWith({
|
||||
userId: ctx.userId,
|
||||
})
|
||||
})
|
||||
|
||||
describe('when called as a callback', function () {
|
||||
it('should expire the user', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import Helpers from './lib/helpers.mjs'
|
||||
|
||||
const tags = ['saas']
|
||||
|
||||
const indexes = [
|
||||
{
|
||||
key: {
|
||||
managerIds: 1,
|
||||
},
|
||||
name: 'managerIds_1',
|
||||
},
|
||||
]
|
||||
|
||||
const migrate = async client => {
|
||||
const { db } = client
|
||||
|
||||
await Helpers.addIndexesToCollection(db.institutions, indexes)
|
||||
}
|
||||
|
||||
const rollback = async client => {
|
||||
const { db } = client
|
||||
|
||||
try {
|
||||
await Helpers.dropIndexesFromCollection(db.institutions, indexes)
|
||||
} catch (err) {
|
||||
console.error('Something went wrong rolling back the migrations', err)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
tags,
|
||||
migrate,
|
||||
rollback,
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import Helpers from './lib/helpers.mjs'
|
||||
|
||||
const tags = ['saas']
|
||||
|
||||
const indexes = [
|
||||
{
|
||||
key: {
|
||||
managerIds: 1,
|
||||
},
|
||||
name: 'managerIds_1',
|
||||
},
|
||||
]
|
||||
|
||||
const migrate = async client => {
|
||||
const { db } = client
|
||||
|
||||
await Helpers.addIndexesToCollection(db.publishers, indexes)
|
||||
}
|
||||
|
||||
const rollback = async client => {
|
||||
const { db } = client
|
||||
|
||||
try {
|
||||
await Helpers.dropIndexesFromCollection(db.publishers, indexes)
|
||||
} catch (err) {
|
||||
console.error('Something went wrong rolling back the migrations', err)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
tags,
|
||||
migrate,
|
||||
rollback,
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import Helpers from './lib/helpers.mjs'
|
||||
|
||||
const tags = ['saas']
|
||||
|
||||
const indexes = [
|
||||
{
|
||||
name: 'ssoConfig_1',
|
||||
key: { ssoConfig: 1 },
|
||||
partialFilterExpression: {
|
||||
ssoConfig: { $exists: true },
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const migrate = async client => {
|
||||
const { db } = client
|
||||
await Helpers.addIndexesToCollection(db.subscriptions, indexes)
|
||||
}
|
||||
|
||||
const rollback = async client => {
|
||||
const { db } = client
|
||||
await Helpers.dropIndexesFromCollection(db.subscriptions, indexes)
|
||||
}
|
||||
|
||||
export default {
|
||||
tags,
|
||||
migrate,
|
||||
rollback,
|
||||
}
|
||||
29
tools/migrations/20251103164405_users_isAdmin_index.mjs
Normal file
29
tools/migrations/20251103164405_users_isAdmin_index.mjs
Normal file
@@ -0,0 +1,29 @@
|
||||
import Helpers from './lib/helpers.mjs'
|
||||
|
||||
const tags = ['server-ce', 'server-pro']
|
||||
|
||||
const indexes = [
|
||||
{
|
||||
name: 'isAdmin_1',
|
||||
key: { isAdmin: 1 },
|
||||
partialFilterExpression: {
|
||||
isAdmin: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const migrate = async client => {
|
||||
const { db } = client
|
||||
await Helpers.addIndexesToCollection(db.users, indexes)
|
||||
}
|
||||
|
||||
const rollback = async client => {
|
||||
const { db } = client
|
||||
await Helpers.dropIndexesFromCollection(db.users, indexes)
|
||||
}
|
||||
|
||||
export default {
|
||||
tags,
|
||||
migrate,
|
||||
rollback,
|
||||
}
|
||||
Reference in New Issue
Block a user