Merge pull request #11869 from overleaf/em-upgrade-mongoose-web

Upgrade Mongoose and the Mongo driver in web

GitOrigin-RevId: 2cad1aabe57eae424a9e4c68b2e0062f0e78ffaf
This commit is contained in:
Eric Mc Sween 2023-02-28 08:26:18 -05:00 committed by Copybot
parent 076bc9b39c
commit 65976cb363
43 changed files with 660 additions and 474 deletions

205
package-lock.json generated
View file

@ -34519,8 +34519,8 @@
"minimist": "^1.2.7",
"mmmagic": "^0.5.3",
"moment": "^2.29.4",
"mongodb": "~3.6.0",
"mongoose": "^5.13.11",
"mongodb": "^4.13.0",
"mongoose": "^6.9.1",
"multer": "overleaf/multer#e1df247fbf8e7590520d20ae3601eaef9f3d2e9e",
"nocache": "^2.1.0",
"nock": "^13.1.3",
@ -34910,6 +34910,40 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"services/web/node_modules/bson": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz",
"integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==",
"dependencies": {
"buffer": "^5.6.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"services/web/node_modules/bson/node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"services/web/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@ -35934,6 +35968,14 @@
"graceful-fs": "^4.1.6"
}
},
"services/web/node_modules/kareem": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz",
"integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==",
"engines": {
"node": ">=12.0.0"
}
},
"services/web/node_modules/karma-webpack": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz",
@ -36094,41 +36136,60 @@
}
},
"services/web/node_modules/mongodb": {
"version": "3.6.12",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.12.tgz",
"integrity": "sha512-ErHpF4P4disEIQB8Nns2twIMVXcvmlwjpKqfVnyB/hhd/L5We48LfoBYjBjuUSiSqL6ffmcygPTgjvpy2LETRQ==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.13.0.tgz",
"integrity": "sha512-+taZ/bV8d1pYuHL4U+gSwkhmDrwkWbH1l4aah4YpmpscMwgFBkufIKxgP/G7m87/NUuQzc2Z75ZTI7ZOyqZLbw==",
"dependencies": {
"bl": "^2.2.1",
"bson": "^1.1.4",
"denque": "^1.4.1",
"optional-require": "^1.0.3",
"safe-buffer": "^5.1.2"
"bson": "^4.7.0",
"mongodb-connection-string-url": "^2.5.4",
"socks": "^2.7.1"
},
"engines": {
"node": ">=4"
"node": ">=12.9.0"
},
"optionalDependencies": {
"saslprep": "^1.0.0"
"@aws-sdk/credential-providers": "^3.186.0",
"saslprep": "^1.0.3"
}
},
"services/web/node_modules/mongoose": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.9.1.tgz",
"integrity": "sha512-hOz1ZWV0w6WEVLrj89Wpk7PXDYtDDF6k7/NX79lY5iKqeFtZsceBXW8xW59YFNcW5O3cH32hQ8IbDlhgyBsDMA==",
"dependencies": {
"bson": "^4.7.0",
"kareem": "2.5.1",
"mongodb": "4.13.0",
"mpath": "0.9.0",
"mquery": "4.0.3",
"ms": "2.1.3",
"sift": "16.0.1"
},
"peerDependenciesMeta": {
"aws4": {
"optional": true
},
"bson-ext": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"mongodb-extjson": {
"optional": true
},
"snappy": {
"optional": true
}
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"services/web/node_modules/mpath": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
"engines": {
"node": ">=4.0.0"
}
},
"services/web/node_modules/mquery": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
"integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
"dependencies": {
"debug": "4.x"
},
"engines": {
"node": ">=12.0.0"
}
},
"services/web/node_modules/multer": {
@ -36814,6 +36875,11 @@
"stack-trace": "0.0.10"
}
},
"services/web/node_modules/sift": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz",
"integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ=="
},
"services/web/node_modules/sinon": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz",
@ -43734,8 +43800,8 @@
"mocha-each": "^2.0.1",
"mock-fs": "^5.1.2",
"moment": "^2.29.4",
"mongodb": "~3.6.0",
"mongoose": "^5.13.11",
"mongodb": "^4.13.0",
"mongoose": "^6.9.1",
"multer": "overleaf/multer#e1df247fbf8e7590520d20ae3601eaef9f3d2e9e",
"nocache": "^2.1.0",
"nock": "^13.1.1",
@ -43993,6 +44059,25 @@
"integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==",
"dev": true
},
"bson": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz",
"integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==",
"requires": {
"buffer": "^5.6.0"
},
"dependencies": {
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
}
}
},
"buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@ -44710,6 +44795,11 @@
"graceful-fs": "^4.1.6"
}
},
"kareem": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz",
"integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA=="
},
"karma-webpack": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz",
@ -44829,16 +44919,42 @@
}
},
"mongodb": {
"version": "3.6.12",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.12.tgz",
"integrity": "sha512-ErHpF4P4disEIQB8Nns2twIMVXcvmlwjpKqfVnyB/hhd/L5We48LfoBYjBjuUSiSqL6ffmcygPTgjvpy2LETRQ==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.13.0.tgz",
"integrity": "sha512-+taZ/bV8d1pYuHL4U+gSwkhmDrwkWbH1l4aah4YpmpscMwgFBkufIKxgP/G7m87/NUuQzc2Z75ZTI7ZOyqZLbw==",
"requires": {
"bl": "^2.2.1",
"bson": "^1.1.4",
"denque": "^1.4.1",
"optional-require": "^1.0.3",
"safe-buffer": "^5.1.2",
"saslprep": "^1.0.0"
"@aws-sdk/credential-providers": "^3.186.0",
"bson": "^4.7.0",
"mongodb-connection-string-url": "^2.5.4",
"saslprep": "^1.0.3",
"socks": "^2.7.1"
}
},
"mongoose": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.9.1.tgz",
"integrity": "sha512-hOz1ZWV0w6WEVLrj89Wpk7PXDYtDDF6k7/NX79lY5iKqeFtZsceBXW8xW59YFNcW5O3cH32hQ8IbDlhgyBsDMA==",
"requires": {
"bson": "^4.7.0",
"kareem": "2.5.1",
"mongodb": "4.13.0",
"mpath": "0.9.0",
"mquery": "4.0.3",
"ms": "2.1.3",
"sift": "16.0.1"
}
},
"mpath": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
},
"mquery": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
"integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
"requires": {
"debug": "4.x"
}
},
"multer": {
@ -45289,6 +45405,11 @@
"stack-trace": "0.0.10"
}
},
"sift": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz",
"integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ=="
},
"sinon": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz",

View file

@ -109,7 +109,7 @@ const AuthenticationManager = {
if (err) {
return callback(err)
}
if (result.nModified !== 1) {
if (result.modifiedCount !== 1) {
return callback(new ParallelLoginError())
}
if (!match) {

View file

@ -234,7 +234,7 @@ async function setCollaboratorPrivilegeLevel(
}
}
const mongoResponse = await Project.updateOne(query, update).exec()
if (mongoResponse.n === 0) {
if (mongoResponse.matchedCount === 0) {
throw new Errors.NotFoundError('project or collaborator not found')
}
}

View file

@ -18,7 +18,7 @@ const ProjectHistoryHandler = {
if (err) {
return callback(err)
}
if (result.n === 0) {
if (result.matchedCount === 0) {
return callback(new Error('history exists'))
}
callback()
@ -57,7 +57,7 @@ const ProjectHistoryHandler = {
return callback(err)
}
// return an error if overleaf.history.id wasn't present
if (result.n === 0) {
if (result.matchedCount === 0) {
return callback(new Error('history not upgraded'))
}
callback()
@ -76,7 +76,7 @@ const ProjectHistoryHandler = {
if (err) {
return callback(err)
}
if (result.n === 0) {
if (result.matchedCount === 0) {
return callback(new Error('history not downgraded'))
}
callback()
@ -94,7 +94,7 @@ const ProjectHistoryHandler = {
if (err) {
return callback(err)
}
if (result.n === 0) {
if (result.matchedCount === 0) {
return callback(new Error('migration flag not set'))
}
callback()

View file

@ -12,20 +12,14 @@ if (
)
}
mongoose.set('autoIndex', false)
mongoose.set('strictQuery', false)
const connectionPromise = mongoose.connect(
Settings.mongo.url,
Object.assign(
{
// mongoose specific config
config: { autoIndex: false },
// mongoose defaults to false, native driver defaults to true
useNewUrlParser: true,
// use the equivalent `findOneAndUpdate` methods of the native driver
useFindAndModify: false,
},
Settings.mongo.options
)
Settings.mongo.options
)
addConnectionDrainer('mongoose', async () => {
await connectionPromise
await mongoose.disconnect()

View file

@ -14,7 +14,7 @@ const DeletedFileSchema = new Schema(
},
deletedAt: { type: Date },
},
{ collection: 'deletedFiles' }
{ collection: 'deletedFiles', minimize: false }
)
exports.DeletedFile = mongoose.model('DeletedFile', DeletedFileSchema)

View file

@ -26,7 +26,7 @@ const DeletedProjectSchema = new Schema(
deleterData: DeleterDataSchema,
project: ProjectSchema,
},
{ collection: 'deletedProjects' }
{ collection: 'deletedProjects', minimize: false }
)
exports.DeletedProject = mongoose.model('DeletedProject', DeletedProjectSchema)

View file

@ -23,7 +23,7 @@ const DeletedSubscriptionSchema = new Schema(
deleterData: DeleterDataSchema,
subscription: SubscriptionSchema,
},
{ collection: 'deletedSubscriptions' }
{ collection: 'deletedSubscriptions', minimize: false }
)
exports.DeletedSubscription = mongoose.model(

View file

@ -23,7 +23,7 @@ const DeletedUserSchema = new Schema(
deleterData: DeleterDataSchema,
user: UserSchema,
},
{ collection: 'deletedUsers' }
{ collection: 'deletedUsers', minimize: false }
)
exports.DeletedUser = mongoose.model('DeletedUser', DeletedUserSchema)

View file

@ -2,9 +2,12 @@ const mongoose = require('../infrastructure/Mongoose')
const { Schema } = mongoose
const DocSchema = new Schema({
name: { type: String, default: 'new doc' },
})
const DocSchema = new Schema(
{
name: { type: String, default: 'new doc' },
},
{ minimize: false }
)
exports.Doc = mongoose.model('Doc', DocSchema)

View file

@ -12,7 +12,7 @@ const DocSnapshotSchema = new Schema(
ranges: Schema.Types.Mixed,
ts: Date,
},
{ collection: 'docSnapshots' }
{ collection: 'docSnapshots', minimize: false }
)
exports.DocSnapshot = mongoose.model('DocSnapshot', DocSnapshotSchema)

View file

@ -2,20 +2,23 @@ const mongoose = require('../infrastructure/Mongoose')
const { Schema } = mongoose
const { ObjectId } = Schema
const FeedbackSchema = new Schema({
userId: {
type: ObjectId,
ref: 'User',
},
source: String,
createdAt: {
type: Date,
default() {
return new Date()
const FeedbackSchema = new Schema(
{
userId: {
type: ObjectId,
ref: 'User',
},
source: String,
createdAt: {
type: Date,
default() {
return new Date()
},
},
data: {},
},
data: {},
})
{ minimize: false }
)
exports.Feedback = mongoose.model('Feedback', FeedbackSchema)
exports.FeedbackSchema = FeedbackSchema

View file

@ -2,23 +2,26 @@ const mongoose = require('../infrastructure/Mongoose')
const { Schema } = mongoose
const FileSchema = new Schema({
name: {
type: String,
default: '',
},
created: {
type: Date,
default() {
return new Date()
const FileSchema = new Schema(
{
name: {
type: String,
default: '',
},
created: {
type: Date,
default() {
return new Date()
},
},
rev: { type: Number, default: 0 },
linkedFileData: { type: Schema.Types.Mixed },
hash: {
type: String,
},
},
rev: { type: Number, default: 0 },
linkedFileData: { type: Schema.Types.Mixed },
hash: {
type: String,
},
})
{ minimize: false }
)
exports.File = mongoose.model('File', FileSchema)
exports.FileSchema = FileSchema

View file

@ -4,9 +4,12 @@ const { FileSchema } = require('./File')
const { Schema } = mongoose
const FolderSchema = new Schema({
name: { type: String, default: 'new folder' },
})
const FolderSchema = new Schema(
{
name: { type: String, default: 'new folder' },
},
{ minimize: false }
)
FolderSchema.add({
docs: [DocSchema],

View file

@ -5,14 +5,17 @@ const settings = require('@overleaf/settings')
const logger = require('@overleaf/logger')
const request = require('request')
const InstitutionSchema = new Schema({
v1Id: { type: Number, required: true },
managerIds: [{ type: ObjectId, ref: 'User' }],
metricsEmail: {
optedOutUserIds: [{ type: ObjectId, ref: 'User' }],
lastSent: { type: Date },
const InstitutionSchema = new Schema(
{
v1Id: { type: Number, required: true },
managerIds: [{ type: ObjectId, ref: 'User' }],
metricsEmail: {
optedOutUserIds: [{ type: ObjectId, ref: 'User' }],
lastSent: { type: Date },
},
},
})
{ minimize: false }
)
// fetch institution's data from v1 API. Errors are ignored
InstitutionSchema.method('fetchV1Data', function (callback) {

View file

@ -15,6 +15,7 @@ const OauthAccessTokenSchema = new Schema(
},
{
collection: 'oauthAccessTokens',
minimize: false,
}
)

View file

@ -13,6 +13,7 @@ const OauthApplicationSchema = new Schema(
},
{
collection: 'oauthApplications',
minimize: false,
}
)

View file

@ -14,6 +14,7 @@ const OauthAuthorizationCodeSchema = new Schema(
},
{
collection: 'oauthAuthorizationCodes',
minimize: false,
}
)

View file

@ -24,92 +24,95 @@ const DeletedFileSchema = new Schema({
deletedAt: { type: Date },
})
const ProjectSchema = new Schema({
name: { type: String, default: 'new project' },
lastUpdated: {
type: Date,
default() {
return new Date()
},
},
lastUpdatedBy: { type: ObjectId, ref: 'User' },
lastOpened: { type: Date },
active: { type: Boolean, default: true },
owner_ref: { type: ObjectId, ref: 'User' },
collaberator_refs: [{ type: ObjectId, ref: 'User' }],
readOnly_refs: [{ type: ObjectId, ref: 'User' }],
rootDoc_id: { type: ObjectId },
rootFolder: [FolderSchema],
version: { type: Number }, // incremented for every change in the project structure (folders and filenames)
publicAccesLevel: { type: String, default: 'private' },
compiler: { type: String, default: 'pdflatex' },
spellCheckLanguage: { type: String, default: 'en' },
deletedByExternalDataSource: { type: Boolean, default: false },
description: { type: String, default: '' },
archived: { type: Schema.Types.Mixed },
trashed: [{ type: ObjectId, ref: 'User' }],
deletedDocs: [DeletedDocSchema],
deletedFiles: [DeletedFileSchema],
imageName: { type: String },
brandVariationId: { type: String },
track_changes: { type: Object },
tokens: {
readOnly: {
type: String,
index: {
unique: true,
partialFilterExpression: { 'tokens.readOnly': { $exists: true } },
const ProjectSchema = new Schema(
{
name: { type: String, default: 'new project' },
lastUpdated: {
type: Date,
default() {
return new Date()
},
},
readAndWrite: {
type: String,
index: {
unique: true,
partialFilterExpression: { 'tokens.readAndWrite': { $exists: true } },
lastUpdatedBy: { type: ObjectId, ref: 'User' },
lastOpened: { type: Date },
active: { type: Boolean, default: true },
owner_ref: { type: ObjectId, ref: 'User' },
collaberator_refs: [{ type: ObjectId, ref: 'User' }],
readOnly_refs: [{ type: ObjectId, ref: 'User' }],
rootDoc_id: { type: ObjectId },
rootFolder: [FolderSchema],
version: { type: Number }, // incremented for every change in the project structure (folders and filenames)
publicAccesLevel: { type: String, default: 'private' },
compiler: { type: String, default: 'pdflatex' },
spellCheckLanguage: { type: String, default: 'en' },
deletedByExternalDataSource: { type: Boolean, default: false },
description: { type: String, default: '' },
archived: { type: Schema.Types.Mixed },
trashed: [{ type: ObjectId, ref: 'User' }],
deletedDocs: [DeletedDocSchema],
deletedFiles: [DeletedFileSchema],
imageName: { type: String },
brandVariationId: { type: String },
track_changes: { type: Object },
tokens: {
readOnly: {
type: String,
index: {
unique: true,
partialFilterExpression: { 'tokens.readOnly': { $exists: true } },
},
},
},
readAndWritePrefix: {
type: String,
index: {
unique: true,
partialFilterExpression: {
'tokens.readAndWritePrefix': { $exists: true },
readAndWrite: {
type: String,
index: {
unique: true,
partialFilterExpression: { 'tokens.readAndWrite': { $exists: true } },
},
},
readAndWritePrefix: {
type: String,
index: {
unique: true,
partialFilterExpression: {
'tokens.readAndWritePrefix': { $exists: true },
},
},
},
},
},
tokenAccessReadOnly_refs: [{ type: ObjectId, ref: 'User' }],
tokenAccessReadAndWrite_refs: [{ type: ObjectId, ref: 'User' }],
fromV1TemplateId: { type: Number },
fromV1TemplateVersionId: { type: Number },
overleaf: {
id: { type: Number },
imported_at_ver_id: { type: Number },
token: { type: String },
read_token: { type: String },
history: {
id: { type: Schema.Types.Mixed },
display: { type: Boolean },
upgradedAt: { type: Date },
allowDowngrade: { type: Boolean },
zipFileArchivedInProject: { type: Boolean },
},
},
collabratecUsers: [
{
user_id: { type: ObjectId, ref: 'User' },
collabratec_document_id: { type: String },
collabratec_privategroup_id: { type: String },
added_at: {
type: Date,
default() {
return new Date()
},
tokenAccessReadOnly_refs: [{ type: ObjectId, ref: 'User' }],
tokenAccessReadAndWrite_refs: [{ type: ObjectId, ref: 'User' }],
fromV1TemplateId: { type: Number },
fromV1TemplateVersionId: { type: Number },
overleaf: {
id: { type: Number },
imported_at_ver_id: { type: Number },
token: { type: String },
read_token: { type: String },
history: {
id: { type: Schema.Types.Mixed },
display: { type: Boolean },
upgradedAt: { type: Date },
allowDowngrade: { type: Boolean },
zipFileArchivedInProject: { type: Boolean },
},
},
],
deferredTpdsFlushCounter: { type: Number },
})
collabratecUsers: [
{
user_id: { type: ObjectId, ref: 'User' },
collabratec_document_id: { type: String },
collabratec_privategroup_id: { type: String },
added_at: {
type: Date,
default() {
return new Date()
},
},
},
],
deferredTpdsFlushCounter: { type: Number },
},
{ minimize: false }
)
ProjectSchema.statics.getProject = function (projectOrId, fields, callback) {
if (projectOrId._id != null) {

View file

@ -11,6 +11,7 @@ const ProjectAuditLogEntrySchema = new Schema(
},
{
collection: 'projectAuditLogEntries',
minimize: false,
}
)

View file

@ -15,7 +15,7 @@ const ProjectHistoryFailureSchema = new Schema(
resyncAttempts: Number,
requestCount: Number,
},
{ collection: 'projectHistoryFailures' }
{ collection: 'projectHistoryFailures', minimize: false }
)
exports.ProjectHistoryFailure = mongoose.model(

View file

@ -27,6 +27,7 @@ const ProjectInviteSchema = new Schema(
},
{
collection: 'projectInvites',
minimize: false,
}
)

View file

@ -5,10 +5,13 @@ const settings = require('@overleaf/settings')
const logger = require('@overleaf/logger')
const request = require('request')
const PublisherSchema = new Schema({
slug: { type: String, required: true },
managerIds: [{ type: ObjectId, ref: 'User' }],
})
const PublisherSchema = new Schema(
{
slug: { type: String, required: true },
managerIds: [{ type: ObjectId, ref: 'User' }],
},
{ minimize: false }
)
// fetch publisher's (brand on v1) data from v1 API. Errors are ignored
PublisherSchema.method('fetchV1Data', function (callback) {

View file

@ -8,6 +8,7 @@ const SamlCacheSchema = new Schema(
},
{
collection: 'samlCache',
minimize: false,
}
)

View file

@ -14,6 +14,7 @@ const SamlLogSchema = new Schema(
},
{
collection: 'samlLogs',
minimize: false,
}
)

View file

@ -96,59 +96,62 @@ const VersionSchema = new Schema(
{ _id: false }
)
const SplitTestSchema = new Schema({
name: {
type: String,
minLength: MIN_NAME_LENGTH,
maxlength: MAX_NAME_LENGTH,
required: true,
unique: true,
validate: {
validator: function (input) {
return input !== null && NAME_REGEX.test(input)
const SplitTestSchema = new Schema(
{
name: {
type: String,
minLength: MIN_NAME_LENGTH,
maxlength: MAX_NAME_LENGTH,
required: true,
unique: true,
validate: {
validator: function (input) {
return input !== null && NAME_REGEX.test(input)
},
message: `invalid, must match: ${NAME_REGEX}`,
},
message: `invalid, must match: ${NAME_REGEX}`,
},
versions: [VersionSchema],
forbidReleasePhase: {
type: Boolean,
required: false,
},
description: {
type: String,
required: false,
},
expectedEndDate: {
type: Date,
required: false,
},
ticketUrl: {
type: String,
required: false,
},
reportsUrls: {
type: [String],
required: false,
default: [],
},
winningVariant: {
type: String,
required: false,
},
archived: {
type: Boolean,
required: false,
},
archivedAt: {
type: Date,
required: false,
},
badgeInfo: {
type: BadgeInfoSchema,
required: false,
},
},
versions: [VersionSchema],
forbidReleasePhase: {
type: Boolean,
required: false,
},
description: {
type: String,
required: false,
},
expectedEndDate: {
type: Date,
required: false,
},
ticketUrl: {
type: String,
required: false,
},
reportsUrls: {
type: [String],
required: false,
default: [],
},
winningVariant: {
type: String,
required: false,
},
archived: {
type: Boolean,
required: false,
},
archivedAt: {
type: Date,
required: false,
},
badgeInfo: {
type: BadgeInfoSchema,
required: false,
},
})
{ minimize: false }
)
module.exports = {
SplitTest: mongoose.model('SplitTest', SplitTestSchema),

View file

@ -4,52 +4,55 @@ const { TeamInviteSchema } = require('./TeamInvite')
const { Schema } = mongoose
const { ObjectId } = Schema
const SubscriptionSchema = new Schema({
admin_id: {
type: ObjectId,
ref: 'User',
index: { unique: true, dropDups: true },
},
manager_ids: {
type: [ObjectId],
ref: 'User',
required: true,
validate: function (managers) {
// require at least one manager
return !!managers.length
const SubscriptionSchema = new Schema(
{
admin_id: {
type: ObjectId,
ref: 'User',
index: { unique: true, dropDups: true },
},
},
member_ids: [{ type: ObjectId, ref: 'User' }],
invited_emails: [String],
teamInvites: [TeamInviteSchema],
recurlySubscription_id: String,
teamName: { type: String },
teamNotice: { type: String },
planCode: { type: String },
groupPlan: { type: Boolean, default: false },
membersLimit: { type: Number, default: 0 },
customAccount: Boolean,
overleaf: {
id: {
type: Number,
index: {
unique: true,
partialFilterExpression: { 'overleaf.id': { $exists: true } },
manager_ids: {
type: [ObjectId],
ref: 'User',
required: true,
validate: function (managers) {
// require at least one manager
return !!managers.length
},
},
member_ids: [{ type: ObjectId, ref: 'User' }],
invited_emails: [String],
teamInvites: [TeamInviteSchema],
recurlySubscription_id: String,
teamName: { type: String },
teamNotice: { type: String },
planCode: { type: String },
groupPlan: { type: Boolean, default: false },
membersLimit: { type: Number, default: 0 },
customAccount: Boolean,
overleaf: {
id: {
type: Number,
index: {
unique: true,
partialFilterExpression: { 'overleaf.id': { $exists: true } },
},
},
},
recurlyStatus: {
state: {
type: String,
},
trialStartedAt: {
type: Date,
},
trialEndsAt: {
type: Date,
},
},
},
recurlyStatus: {
state: {
type: String,
},
trialStartedAt: {
type: Date,
},
trialEndsAt: {
type: Date,
},
},
})
{ minimize: false }
)
// Subscriptions have no v1 data to fetch
SubscriptionSchema.method('fetchV1Data', function (callback) {

View file

@ -40,6 +40,7 @@ const SurveySchema = new Schema(
},
{
collection: 'surveys',
minimize: false,
}
)

View file

@ -2,8 +2,11 @@ const mongoose = require('../infrastructure/Mongoose')
const { Schema } = mongoose
const SystemMessageSchema = new Schema({
content: { type: String, default: '' },
})
const SystemMessageSchema = new Schema(
{
content: { type: String, default: '' },
},
{ minimize: false }
)
exports.SystemMessage = mongoose.model('SystemMessage', SystemMessageSchema)

View file

@ -4,11 +4,14 @@ const { Schema } = mongoose
// Note that for legacy reasons, user_id and project_ids are plain strings,
// not ObjectIds.
const TagSchema = new Schema({
user_id: { type: String, required: true },
name: { type: String, required: true },
project_ids: [String],
})
const TagSchema = new Schema(
{
user_id: { type: String, required: true },
name: { type: String, required: true },
project_ids: [String],
},
{ minimize: false }
)
exports.Tag = mongoose.model('Tag', TagSchema)
exports.TagSchema = TagSchema

View file

@ -2,12 +2,15 @@ const mongoose = require('../infrastructure/Mongoose')
const { Schema } = mongoose
const TeamInviteSchema = new Schema({
email: { type: String, required: true },
token: { type: String },
inviterName: { type: String },
sentAt: { type: Date },
})
const TeamInviteSchema = new Schema(
{
email: { type: String, required: true },
token: { type: String },
inviterName: { type: String },
sentAt: { type: Date },
},
{ minimize: false }
)
exports.TeamInvite = mongoose.model('TeamInvite', TeamInviteSchema)
exports.TeamInviteSchema = TeamInviteSchema

View file

@ -7,173 +7,182 @@ const { ObjectId } = Schema
// See https://stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address/574698#574698
const MAX_EMAIL_LENGTH = 254
const UserSchema = new Schema({
email: { type: String, default: '', maxlength: MAX_EMAIL_LENGTH },
emails: [
{
email: { type: String, default: '', maxlength: MAX_EMAIL_LENGTH },
reversedHostname: { type: String, default: '' },
createdAt: {
type: Date,
default() {
return new Date()
const UserSchema = new Schema(
{
email: { type: String, default: '', maxlength: MAX_EMAIL_LENGTH },
emails: [
{
email: { type: String, default: '', maxlength: MAX_EMAIL_LENGTH },
reversedHostname: { type: String, default: '' },
createdAt: {
type: Date,
default() {
return new Date()
},
},
confirmedAt: { type: Date },
samlProviderId: { type: String },
affiliationUnchecked: { type: Boolean },
reconfirmedAt: { type: Date },
},
],
first_name: { type: String, default: '' },
last_name: { type: String, default: '' },
role: { type: String, default: '' },
institution: { type: String, default: '' },
hashedPassword: String,
isAdmin: { type: Boolean, default: false },
staffAccess: {
publisherMetrics: { type: Boolean, default: false },
publisherManagement: { type: Boolean, default: false },
institutionMetrics: { type: Boolean, default: false },
institutionManagement: { type: Boolean, default: false },
groupMetrics: { type: Boolean, default: false },
groupManagement: { type: Boolean, default: false },
adminMetrics: { type: Boolean, default: false },
splitTestMetrics: { type: Boolean, default: false },
splitTestManagement: { type: Boolean, default: false },
},
signUpDate: {
type: Date,
default() {
return new Date()
},
},
loginEpoch: { type: Number },
lastActive: { type: Date },
lastFailedLogin: { type: Date },
lastLoggedIn: { type: Date },
lastLoginIp: { type: String, default: '' },
lastPrimaryEmailCheck: { type: Date },
loginCount: { type: Number, default: 0 },
holdingAccount: { type: Boolean, default: false },
ace: {
mode: { type: String, default: 'none' },
theme: { type: String, default: 'textmate' },
overallTheme: { type: String, default: '' },
fontSize: { type: Number, default: '12' },
autoComplete: { type: Boolean, default: true },
autoPairDelimiters: { type: Boolean, default: true },
spellCheckLanguage: { type: String, default: 'en' },
pdfViewer: { type: String, default: 'pdfjs' },
syntaxValidation: { type: Boolean },
fontFamily: { type: String },
lineHeight: { type: String },
},
features: {
collaborators: {
type: Number,
default: Settings.defaultFeatures.collaborators,
},
versioning: {
type: Boolean,
default: Settings.defaultFeatures.versioning,
},
dropbox: { type: Boolean, default: Settings.defaultFeatures.dropbox },
github: { type: Boolean, default: Settings.defaultFeatures.github },
gitBridge: { type: Boolean, default: Settings.defaultFeatures.gitBridge },
compileTimeout: {
type: Number,
default: Settings.defaultFeatures.compileTimeout,
},
compileGroup: {
type: String,
default: Settings.defaultFeatures.compileGroup,
},
templates: { type: Boolean, default: Settings.defaultFeatures.templates },
references: {
type: Boolean,
default: Settings.defaultFeatures.references,
},
trackChanges: {
type: Boolean,
default: Settings.defaultFeatures.trackChanges,
},
mendeley: { type: Boolean, default: Settings.defaultFeatures.mendeley },
zotero: { type: Boolean, default: Settings.defaultFeatures.zotero },
referencesSearch: {
type: Boolean,
default: Settings.defaultFeatures.referencesSearch,
},
symbolPalette: {
type: Boolean,
default: Settings.defaultFeatures.symbolPalette,
},
},
featuresOverrides: [
{
createdAt: {
type: Date,
default() {
return new Date()
},
},
expiresAt: { type: Date },
note: { type: String },
features: {
collaborators: { type: Number },
versioning: { type: Boolean },
dropbox: { type: Boolean },
github: { type: Boolean },
gitBridge: { type: Boolean },
compileTimeout: { type: Number },
compileGroup: { type: String },
templates: { type: Boolean },
trackChanges: { type: Boolean },
mendeley: { type: Boolean },
zotero: { type: Boolean },
referencesSearch: { type: Boolean },
symbolPalette: { type: Boolean },
},
},
confirmedAt: { type: Date },
samlProviderId: { type: String },
affiliationUnchecked: { type: Boolean },
reconfirmedAt: { type: Date },
},
],
first_name: { type: String, default: '' },
last_name: { type: String, default: '' },
role: { type: String, default: '' },
institution: { type: String, default: '' },
hashedPassword: String,
isAdmin: { type: Boolean, default: false },
staffAccess: {
publisherMetrics: { type: Boolean, default: false },
publisherManagement: { type: Boolean, default: false },
institutionMetrics: { type: Boolean, default: false },
institutionManagement: { type: Boolean, default: false },
groupMetrics: { type: Boolean, default: false },
groupManagement: { type: Boolean, default: false },
adminMetrics: { type: Boolean, default: false },
splitTestMetrics: { type: Boolean, default: false },
splitTestManagement: { type: Boolean, default: false },
},
signUpDate: {
type: Date,
default() {
return new Date()
},
},
loginEpoch: { type: Number },
lastActive: { type: Date },
lastFailedLogin: { type: Date },
lastLoggedIn: { type: Date },
lastLoginIp: { type: String, default: '' },
lastPrimaryEmailCheck: { type: Date },
loginCount: { type: Number, default: 0 },
holdingAccount: { type: Boolean, default: false },
ace: {
mode: { type: String, default: 'none' },
theme: { type: String, default: 'textmate' },
overallTheme: { type: String, default: '' },
fontSize: { type: Number, default: '12' },
autoComplete: { type: Boolean, default: true },
autoPairDelimiters: { type: Boolean, default: true },
spellCheckLanguage: { type: String, default: 'en' },
pdfViewer: { type: String, default: 'pdfjs' },
syntaxValidation: { type: Boolean },
fontFamily: { type: String },
lineHeight: { type: String },
},
features: {
collaborators: {
type: Number,
default: Settings.defaultFeatures.collaborators,
},
versioning: { type: Boolean, default: Settings.defaultFeatures.versioning },
dropbox: { type: Boolean, default: Settings.defaultFeatures.dropbox },
github: { type: Boolean, default: Settings.defaultFeatures.github },
gitBridge: { type: Boolean, default: Settings.defaultFeatures.gitBridge },
compileTimeout: {
type: Number,
default: Settings.defaultFeatures.compileTimeout,
},
compileGroup: {
],
featuresUpdatedAt: { type: Date },
featuresEpoch: {
type: String,
default: Settings.defaultFeatures.compileGroup,
},
templates: { type: Boolean, default: Settings.defaultFeatures.templates },
references: { type: Boolean, default: Settings.defaultFeatures.references },
trackChanges: {
type: Boolean,
default: Settings.defaultFeatures.trackChanges,
},
mendeley: { type: Boolean, default: Settings.defaultFeatures.mendeley },
zotero: { type: Boolean, default: Settings.defaultFeatures.zotero },
referencesSearch: {
type: Boolean,
default: Settings.defaultFeatures.referencesSearch,
},
symbolPalette: {
type: Boolean,
default: Settings.defaultFeatures.symbolPalette,
},
},
featuresOverrides: [
{
createdAt: {
type: Date,
default() {
return new Date()
},
},
expiresAt: { type: Date },
note: { type: String },
features: {
collaborators: { type: Number },
versioning: { type: Boolean },
dropbox: { type: Boolean },
github: { type: Boolean },
gitBridge: { type: Boolean },
compileTimeout: { type: Number },
compileGroup: { type: String },
templates: { type: Boolean },
trackChanges: { type: Boolean },
mendeley: { type: Boolean },
zotero: { type: Boolean },
referencesSearch: { type: Boolean },
symbolPalette: { type: Boolean },
// when auto-merged from SL and must-reconfirm is set, we may end up using
// `sharelatexHashedPassword` to recover accounts...
sharelatexHashedPassword: String,
must_reconfirm: { type: Boolean, default: false },
referal_id: {
type: String,
default() {
return TokenGenerator.generateReferralId()
},
},
],
featuresUpdatedAt: { type: Date },
featuresEpoch: {
type: String,
},
// when auto-merged from SL and must-reconfirm is set, we may end up using
// `sharelatexHashedPassword` to recover accounts...
sharelatexHashedPassword: String,
must_reconfirm: { type: Boolean, default: false },
referal_id: {
type: String,
default() {
return TokenGenerator.generateReferralId()
refered_users: [{ type: ObjectId, ref: 'User' }],
refered_user_count: { type: Number, default: 0 },
refProviders: {
// The actual values are managed by third-party-references.
mendeley: Schema.Types.Mixed,
zotero: Schema.Types.Mixed,
},
alphaProgram: { type: Boolean, default: false }, // experimental features
betaProgram: { type: Boolean, default: false },
labsProgram: { type: Boolean, default: false },
labsProgramGalileo: { type: Boolean, default: false },
overleaf: {
id: { type: Number },
accessToken: { type: String },
refreshToken: { type: String },
},
awareOfV2: { type: Boolean, default: false },
samlIdentifiers: { type: Array, default: [] },
thirdPartyIdentifiers: { type: Array, default: [] },
migratedAt: { type: Date },
twoFactorAuthentication: {
createdAt: { type: Date },
enrolledAt: { type: Date },
secretEncrypted: { type: String },
},
onboardingEmailSentAt: { type: Date },
splitTests: Schema.Types.Mixed,
analyticsId: { type: String },
surveyResponses: Schema.Types.Mixed,
},
refered_users: [{ type: ObjectId, ref: 'User' }],
refered_user_count: { type: Number, default: 0 },
refProviders: {
// The actual values are managed by third-party-references.
mendeley: Schema.Types.Mixed,
zotero: Schema.Types.Mixed,
},
alphaProgram: { type: Boolean, default: false }, // experimental features
betaProgram: { type: Boolean, default: false },
labsProgram: { type: Boolean, default: false },
labsProgramGalileo: { type: Boolean, default: false },
overleaf: {
id: { type: Number },
accessToken: { type: String },
refreshToken: { type: String },
},
awareOfV2: { type: Boolean, default: false },
samlIdentifiers: { type: Array, default: [] },
thirdPartyIdentifiers: { type: Array, default: [] },
migratedAt: { type: Date },
twoFactorAuthentication: {
createdAt: { type: Date },
enrolledAt: { type: Date },
secretEncrypted: { type: String },
},
onboardingEmailSentAt: { type: Date },
splitTests: Schema.Types.Mixed,
analyticsId: { type: String },
surveyResponses: Schema.Types.Mixed,
})
{ minimize: false }
)
function formatSplitTestsSchema(next) {
if (this.splitTests) {

View file

@ -12,6 +12,7 @@ const UserAuditLogEntrySchema = new Schema(
},
{
collection: 'userAuditLogEntries',
minimize: false,
}
)

View file

@ -88,9 +88,7 @@ module.exports = {
mongo: {
options: {
appname: 'web',
useUnifiedTopology:
(process.env.MONGO_USE_UNIFIED_TOPOLOGY || 'true') === 'true',
poolSize: parseInt(process.env.MONGO_POOL_SIZE, 10) || 10,
maxPoolSize: parseInt(process.env.MONGO_POOL_SIZE, 10) || 100,
serverSelectionTimeoutMS:
parseInt(process.env.MONGO_SERVER_SELECTION_TIMEOUT, 10) || 60000,
socketTimeoutMS: parseInt(process.env.MONGO_SOCKET_TIMEOUT, 10) || 60000,

View file

@ -189,8 +189,8 @@
"minimist": "^1.2.7",
"mmmagic": "^0.5.3",
"moment": "^2.29.4",
"mongodb": "~3.6.0",
"mongoose": "^5.13.11",
"mongodb": "^4.13.0",
"mongoose": "^6.9.1",
"multer": "overleaf/multer#e1df247fbf8e7590520d20ae3601eaef9f3d2e9e",
"nocache": "^2.1.0",
"nock": "^13.1.3",

View file

@ -27,7 +27,7 @@ async function main() {
})
if (!DRY_RUN) {
console.log(`updating doc ${DOC_ID} in mongo for project ${PROJECT_ID}`)
const { result } = await db.docs.updateOne(
const result = await db.docs.updateOne(
{ _id: ObjectId(DOC_ID), project_id: ObjectId(PROJECT_ID) },
{
$set: { lines, version, ranges },
@ -38,7 +38,11 @@ async function main() {
}
)
console.log('mongo result', result)
if (result.n !== 1 || result.nModified !== 1 || result.ok !== 1) {
if (
result.matchedCount !== 1 ||
result.modifiedCount !== 1 ||
!result.acknowledged
) {
throw new Error('unexpected result from mongo update')
}
console.log(`deleting doc ${DOC_ID} from redis for project ${PROJECT_ID}`)

View file

@ -54,8 +54,8 @@ async function updateImage(image, projectIds) {
{ _id: { $in: projectIds.map(ObjectId) } },
{ $set: { imageName: `quay.io/sharelatex/${image}` } }
).exec()
console.log(`Found ${res.n} out of ${projectIds.length} projects`)
console.log(`Modified ${res.nModified} projects`)
console.log(`Found ${res.matchedCount} out of ${projectIds.length} projects`)
console.log(`Modified ${res.modifiedCount} projects`)
}
main()

View file

@ -46,7 +46,16 @@ const SandboxedModule = require('sandboxed-module')
SandboxedModule.configure({
ignoreMissing: true,
requires: getSandboxedModuleRequires(),
globals: { AbortSignal, Buffer, Promise, console, process, URL },
globals: {
AbortSignal,
Buffer,
Promise,
console,
process,
URL,
TextEncoder,
TextDecoder,
},
})
function getSandboxedModuleRequires() {

View file

@ -17,7 +17,7 @@ describe('AuthenticationManager', function () {
requires: {
'../../models/User': {
User: (this.User = {
updateOne: sinon.stub().callsArgWith(3, null, { nModified: 1 }),
updateOne: sinon.stub().callsArgWith(3, null, { modifiedCount: 1 }),
}),
},
'../../infrastructure/mongodb': {
@ -99,7 +99,7 @@ describe('AuthenticationManager', function () {
})
it('should return the user', function () {
this.callback.calledWith(null, this.user).should.equal(true)
this.callback.should.have.been.calledWith(null, this.user)
})
it('should send metrics', function () {
@ -147,7 +147,7 @@ describe('AuthenticationManager', function () {
beforeEach(function () {
this.User.updateOne = sinon
.stub()
.callsArgWith(3, null, { nModified: 0 })
.callsArgWith(3, null, { modifiedCount: 0 })
})
describe('correct password', function () {
@ -171,7 +171,9 @@ describe('AuthenticationManager', function () {
describe('bad password', function () {
beforeEach(function (done) {
this.User.updateOne = sinon.stub().yields(null, { nModified: 0 })
this.User.updateOne = sinon
.stub()
.yields(null, { modifiedCount: 0 })
this.AuthenticationManager.authenticate(
{ email: this.email },
'notthecorrectpassword',

View file

@ -506,7 +506,7 @@ describe('CollaboratorsHandler', function () {
}
)
.chain('exec')
.resolves({ n: 1 })
.resolves({ matchedCount: 1 })
await this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel(
this.projectId,
this.userId,
@ -530,7 +530,7 @@ describe('CollaboratorsHandler', function () {
}
)
.chain('exec')
.resolves({ n: 1 })
.resolves({ matchedCount: 1 })
await this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel(
this.projectId,
this.userId,
@ -539,7 +539,9 @@ describe('CollaboratorsHandler', function () {
})
it('throws a NotFoundError if the project or collaborator does not exist', async function () {
this.ProjectMock.expects('updateOne').chain('exec').resolves({ n: 0 })
this.ProjectMock.expects('updateOne')
.chain('exec')
.resolves({ matchedCount: 0 })
await expect(
this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel(
this.projectId,

View file

@ -75,7 +75,7 @@ describe('ProjectHistoryHandler', function () {
.callsArgWith(1, null, this.project)
this.ProjectModel.updateOne = sinon
.stub()
.callsArgWith(2, null, { n: 1 })
.callsArgWith(2, null, { matchedCount: 1 })
return this.ProjectHistoryHandler.ensureHistoryExistsForProject(
project_id,
this.callback

View file

@ -40,7 +40,7 @@ describe('UserAuditLogHandler', function () {
beforeEach(function () {
this.dbUpdate = this.UserAuditLogEntryMock.expects('create')
.chain('exec')
.resolves({ nModified: 1 })
.resolves({ modifiedCount: 1 })
})
it('writes a log', async function () {
await this.UserAuditLogHandler.promises.addEntry(

View file

@ -35,9 +35,11 @@ describe('UserCreator', function () {
}),
'./UserUpdater': (this.UserUpdater = {
promises: {
addAffiliationForNewUser: sinon
.stub()
.resolves({ n: 1, nModified: 1, ok: 1 }),
addAffiliationForNewUser: sinon.stub().resolves({
matchedCount: 1,
modifiedCount: 1,
acknowledged: true,
}),
updateUser: sinon.stub().resolves(),
},
}),