[web] use a global shared mock for the metrics module (#32799)

GitOrigin-RevId: 72874ba6c06c2a602b01cc029bc9c71ce3ce8892
This commit is contained in:
Jakob Ackermann
2026-04-14 14:20:43 +02:00
committed by Copybot
parent e9b50b08bf
commit 917d2700c8
30 changed files with 41 additions and 172 deletions

View File

@@ -25,10 +25,6 @@ describe('LaunchpadController', function () {
vi.doMock('@overleaf/settings', () => ({ default: ctx.Settings }))
vi.doMock('@overleaf/metrics', () => ({
default: (ctx.Metrics = {}),
}))
vi.doMock(
'../../../../../app/src/Features/User/UserRegistrationHandler.mjs',
() => ({

View File

@@ -2,6 +2,7 @@ import { afterEach, beforeEach, chai, vi } from 'vitest'
import 'sinon-mongoose'
import sinon from 'sinon'
import logger from '@overleaf/logger'
import Metrics from '@overleaf/metrics'
import sinonChai from 'sinon-chai'
import chaiAsPromised from 'chai-as-promised'
import mongoose from 'mongoose'
@@ -42,7 +43,23 @@ vi.mock('@overleaf/logger', async () => {
// Mock metrics in unit tests, can be overridden
vi.mock('@overleaf/metrics', () => {
return {
default: { prom: { Counter: vi.fn(), Histogram: vi.fn() } },
default: {
inc: sinon.stub(),
count: sinon.stub(),
timing: sinon.stub(),
summary: sinon.stub(),
gauge: sinon.stub(),
Timer: class {
constructor() {
this.labels = {}
}
done() {
return 1
}
},
prom: { Counter: sinon.stub(), Histogram: sinon.stub() },
},
}
})
@@ -56,12 +73,14 @@ beforeEach(ctx => {
}
}
ctx.logger = logger
ctx.Metrics = Metrics
})
afterEach(() => {
vi.resetAllMocks()
vi.resetModules()
sinon.restore()
sinon.resetHistory()
const modelNames = mongoose.modelNames()
modelNames.forEach(name => {
delete mongoose.connection.models[name]

View File

@@ -108,7 +108,6 @@ describe('AuthenticationController', function () {
}),
}))
ctx.Metrics = { inc: sinon.stub() }
hoistedMocks.metricsInc = ctx.Metrics.inc
vi.doMock('../../../../app/src/Features/Security/LoginRateLimiter', () => ({

View File

@@ -14,7 +14,6 @@ describe('AuthenticationManager', function () {
beforeEach(async function (ctx) {
tk.freeze(Date.now())
ctx.settings = { security: { bcryptRounds: 4 } }
ctx.metrics = { inc: sinon.stub().returns() }
ctx.HaveIBeenPwned = {
promises: {
checkPasswordForReuse: sinon.stub().resolves(false),
@@ -73,10 +72,6 @@ describe('AuthenticationManager', function () {
}),
}))
vi.doMock('@overleaf/metrics', () => ({
default: ctx.metrics,
}))
ctx.AuthenticationManager = (await import(modulePath)).default
})
@@ -105,7 +100,7 @@ describe('AuthenticationManager', function () {
ctx.User.findOne = sinon
.stub()
.returns({ exec: sinon.stub().resolves(ctx.user) })
ctx.metrics.inc.reset()
ctx.Metrics.inc.reset()
})
describe('when the hashed password matches', function () {
@@ -143,7 +138,7 @@ describe('AuthenticationManager', function () {
it('should send metrics', function (ctx) {
expect(
ctx.metrics.inc.calledWith('check-password', { status: 'success' })
ctx.Metrics.inc.calledWith('check-password', { status: 'success' })
).to.equal(true)
})
})
@@ -333,7 +328,7 @@ describe('AuthenticationManager', function () {
ctx.User.findOne = sinon
.stub()
.returns({ exec: sinon.stub().resolves(ctx.user) })
ctx.metrics.inc.reset()
ctx.Metrics.inc.reset()
})
describe('when the hashed password matches', function () {
@@ -362,7 +357,7 @@ describe('AuthenticationManager', function () {
it('should send metrics', function (ctx) {
expect(
ctx.metrics.inc.calledWith('check-password', {
ctx.Metrics.inc.calledWith('check-password', {
status: 'too_short',
})
).to.equal(true)
@@ -509,7 +504,7 @@ describe('AuthenticationManager', function () {
})
it('should check the number of rounds', function (ctx) {
expect(ctx.metrics.inc).to.have.been.calledWith(
expect(ctx.Metrics.inc).to.have.been.calledWith(
'bcrypt_check_rounds',
1,
{ status: 'upgrade' }
@@ -546,7 +541,7 @@ describe('AuthenticationManager', function () {
})
it('should not check the number of rounds', function (ctx) {
expect(ctx.metrics.inc).to.have.been.calledWith(
expect(ctx.Metrics.inc).to.have.been.calledWith(
'bcrypt_check_rounds',
1,
{ status: 'disabled' }
@@ -633,12 +628,12 @@ describe('AuthenticationManager', function () {
describe('password length', function () {
describe('with the default password length options', function () {
beforeEach(function (ctx) {
ctx.metrics.inc.reset()
ctx.Metrics.inc.reset()
})
it('should send a metric', function (ctx) {
ctx.AuthenticationManager.validatePassword('foo')
expect(ctx.metrics.inc.calledWith('try-validate-password')).to.equal(
expect(ctx.Metrics.inc.calledWith('try-validate-password')).to.equal(
true
)
})
@@ -832,7 +827,7 @@ describe('AuthenticationManager', function () {
describe('_validatePasswordNotTooSimilar', function () {
beforeEach(function (ctx) {
ctx.metrics.inc.reset()
ctx.Metrics.inc.reset()
})
it('should return an error when the password is too similar to email', function (ctx) {
@@ -1043,7 +1038,7 @@ describe('AuthenticationManager', function () {
beforeEach(function (ctx) {
ctx.user.email = 'foobarbazquux@example.com'
ctx.password = 'foo21barbaz'
ctx.metrics.inc.reset()
ctx.Metrics.inc.reset()
})
it('should produce an error when the password is too similar to the email', async function (ctx) {
@@ -1061,7 +1056,7 @@ describe('AuthenticationManager', function () {
}
expect(
ctx.metrics.inc.calledWith('password-too-similar-to-email')
ctx.Metrics.inc.calledWith('password-too-similar-to-email')
).to.equal(true)
})
@@ -1080,14 +1075,14 @@ describe('AuthenticationManager', function () {
}
expect(
ctx.metrics.inc.calledWith('password-too-similar-to-email')
ctx.Metrics.inc.calledWith('password-too-similar-to-email')
).to.equal(true)
})
})
describe('successful password set attempt', function () {
beforeEach(async function (ctx) {
ctx.metrics.inc.reset()
ctx.Metrics.inc.reset()
ctx.UserGetter.promises.getUser = sinon
.stub()
.resolves({ overleaf: null })
@@ -1119,7 +1114,7 @@ describe('AuthenticationManager', function () {
it('should not send a metric for password-too-similar-to-email', function (ctx) {
expect(
ctx.metrics.inc.calledWith('password-too-similar-to-email')
ctx.Metrics.inc.calledWith('password-too-similar-to-email')
).to.equal(false)
})
})

View File

@@ -22,12 +22,6 @@ describe('BetaProgramHandler', function () {
save: sinon.stub().callsArgWith(0, null),
}
vi.doMock('@overleaf/metrics', () => ({
default: {
inc: sinon.stub(),
},
}))
vi.doMock('../../../../app/src/Features/User/UserUpdater', () => ({
default: (ctx.UserUpdater = {
promises: {

View File

@@ -16,7 +16,6 @@ describe('ClsiCookieManager', function () {
fetchNothing: sinon.stub().returns(Promise.resolve()),
fetchStringWithResponse: sinon.stub().returns(Promise.resolve()),
}
ctx.metrics = { inc: sinon.stub() }
ctx.settings = {
redis: {
web: 'redis.something',
@@ -41,9 +40,6 @@ describe('ClsiCookieManager', function () {
default: ctx.settings,
}))
vi.doMock('@overleaf/fetch-utils', () => ctx.fetchUtils)
vi.doMock('@overleaf/metrics', () => ({
default: ctx.metrics,
}))
ctx.ClsiCookieManager = (await import(modulePath)).default()
})
@@ -296,7 +292,7 @@ describe('ClsiCookieManager', function () {
body: 'previous-clsi-server-id,UP\n',
})
await ctx.call()
expect(ctx.metrics.inc).to.have.been.calledWith(
expect(ctx.Metrics.inc).to.have.been.calledWith(
'clsi-lb-switch-backend',
1,
{ status: 'load-shedding' }
@@ -309,7 +305,7 @@ describe('ClsiCookieManager', function () {
body: 'other-clsi-server-id,UP\n',
})
await ctx.call()
expect(ctx.metrics.inc).to.have.been.calledWith(
expect(ctx.Metrics.inc).to.have.been.calledWith(
'clsi-lb-switch-backend',
1,
{ status: 'cycle' }
@@ -321,7 +317,7 @@ describe('ClsiCookieManager', function () {
response: { status: 404 },
})
await ctx.call()
expect(ctx.metrics.inc).to.have.been.calledWith(
expect(ctx.Metrics.inc).to.have.been.calledWith(
'clsi-lb-switch-backend',
1,
{ status: 'cycle' }

View File

@@ -104,19 +104,6 @@ describe('CompileController', function () {
}),
}))
vi.doMock('@overleaf/metrics', () => ({
default: (ctx.Metrics = {
inc: sinon.stub(),
Timer: class {
constructor() {
this.labels = {}
}
done() {}
},
}),
}))
vi.doMock(
'../../../../app/src/Features/Compile/ClsiCacheController',
() => ({

View File

@@ -68,14 +68,6 @@ describe('DocumentUpdaterHandler', function () {
default: {},
}))
vi.doMock('@overleaf/metrics', () => ({
default: {
Timer: class {
done() {}
},
},
}))
vi.doMock('../../../../app/src/infrastructure/Modules', () => ({
default: {
promises: {

View File

@@ -31,10 +31,6 @@ describe('ProjectDownloadsController', function () {
default: (ctx.ProjectGetter = {}),
}))
vi.doMock('@overleaf/metrics', () => ({
default: (ctx.metrics = {}),
}))
vi.doMock(
'../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler.mjs',
() => ({
@@ -75,7 +71,7 @@ describe('ProjectDownloadsController', function () {
ctx.DocumentUpdaterHandler.flushProjectToMongo = sinon
.stub()
.callsArgWith(1)
ctx.metrics.inc = sinon.stub()
ctx.Metrics.inc = sinon.stub()
return ctx.ProjectDownloadsController.downloadProject(
ctx.req,
ctx.res,
@@ -119,7 +115,7 @@ describe('ProjectDownloadsController', function () {
})
it('should record the action via Metrics', function (ctx) {
return ctx.metrics.inc.calledWith('zip-downloads').should.equal(true)
return ctx.Metrics.inc.calledWith('zip-downloads').should.equal(true)
})
it('should add an audit log entry', function (ctx) {
@@ -152,7 +148,7 @@ describe('ProjectDownloadsController', function () {
ctx.DocumentUpdaterHandler.flushMultipleProjectsToMongo = sinon
.stub()
.callsArgWith(1)
ctx.metrics.inc = sinon.stub()
ctx.Metrics.inc = sinon.stub()
return ctx.ProjectDownloadsController.downloadMultipleProjects(
ctx.req,
ctx.res,
@@ -191,7 +187,7 @@ describe('ProjectDownloadsController', function () {
})
it('should record the action via Metrics', function (ctx) {
return ctx.metrics.inc
return ctx.Metrics.inc
.calledWith('zip-downloads-multiple')
.should.equal(true)
})

View File

@@ -83,10 +83,6 @@ describe('EditorController', function () {
})
)
vi.doMock('@overleaf/metrics', () => ({
default: (ctx.Metrics = { inc: sinon.stub() }),
}))
ctx.EditorController = (await import(modulePath)).default
})

View File

@@ -137,7 +137,6 @@ describe('EditorHttpController', function () {
ctx.ProjectEditorHandler = {
buildProjectModelView: sinon.stub().returns(ctx.projectView),
}
ctx.Metrics = { inc: sinon.stub() }
ctx.TokenAccessHandler = {
getRequestToken: sinon.stub().returns(ctx.token),
}
@@ -195,9 +194,6 @@ describe('EditorHttpController', function () {
default: ctx.EditorController,
})
)
vi.doMock('@overleaf/metrics', () => ({
default: ctx.Metrics,
}))
vi.doMock(
'../../../../app/src/Features/Collaborators/CollaboratorsGetter.mjs',
() => ({

View File

@@ -9,7 +9,6 @@ const modulePath = path.join(
describe('EditorRealTimeController', function () {
beforeEach(async function (ctx) {
ctx.rclient = { publish: sinon.stub() }
ctx.Metrics = { summary: sinon.stub() }
vi.doMock('../../../../app/src/infrastructure/RedisWrapper', () => ({
default: {
@@ -27,10 +26,6 @@ describe('EditorRealTimeController', function () {
default: { redis: {} },
}))
vi.doMock('@overleaf/metrics', () => ({
default: ctx.Metrics,
}))
vi.doMock('node:crypto', () => ({
default: (ctx.crypto = {
randomBytes: sinon

View File

@@ -51,12 +51,6 @@ describe('EmailSender', function () {
() => ctx.RateLimiter
)
vi.doMock('@overleaf/metrics', () => ({
default: {
inc() {},
},
}))
ctx.EmailSender = (await import(MODULE_PATH)).default
ctx.opts = {

View File

@@ -21,9 +21,6 @@ describe('FileStoreController', function () {
ctx.HistoryManager = {
promises: { requestBlobWithProjectId: sinon.stub() },
}
ctx.Metrics = {
inc: sinon.stub(),
}
vi.doMock('node:stream/promises', () => ctx.Stream)
@@ -39,8 +36,6 @@ describe('FileStoreController', function () {
default: ctx.HistoryManager,
}))
vi.doMock('@overleaf/metrics', () => ({ default: ctx.Metrics }))
ctx.controller = (await import(MODULE_PATH)).default
ctx.stream = {}
ctx.projectId = '2k3j1lk3j21lk3j'

View File

@@ -67,10 +67,6 @@ describe('HistoryController', function () {
fetchNothing: ctx.fetchNothing,
}))
vi.doMock('@overleaf/Metrics', () => ({
default: {},
}))
vi.doMock('../../../../app/src/infrastructure/mongodb.mjs', () => ({
default: { ObjectId },
}))

View File

@@ -10,7 +10,6 @@ const modulePath =
describe('InactiveProjectManager', function () {
beforeEach(async function (ctx) {
ctx.settings = {}
ctx.metrics = { inc: sinon.stub() }
ctx.DocstoreManager = {
promises: {
unarchiveProject: sinon.stub(),
@@ -39,10 +38,6 @@ describe('InactiveProjectManager', function () {
default: ctx.settings,
}))
vi.doMock('@overleaf/metrics', () => ({
default: ctx.metrics,
}))
vi.doMock('../../../../app/src/Features/Docstore/DocstoreManager', () => ({
default: ctx.DocstoreManager,
}))

View File

@@ -185,12 +185,6 @@ describe('ProjectController', function () {
flushProjectToTpdsIfNeeded: sinon.stub().resolves(),
},
}
ctx.Metrics = {
Timer: class {
done() {}
},
inc: sinon.stub(),
}
ctx.SplitTestHandler = {
promises: {
getAssignment: sinon.stub().resolves({ variant: 'default' }),
@@ -253,10 +247,6 @@ describe('ProjectController', function () {
default: ctx.settings,
}))
vi.doMock('@overleaf/metrics', () => ({
default: ctx.Metrics,
}))
vi.doMock(
'../../../../app/src/Features/Collaborators/CollaboratorsHandler',
() => ({

View File

@@ -104,8 +104,6 @@ describe('ProjectDetailsHandler', function () {
default: ctx.settings,
}))
vi.doMock('@overleaf/metrics', () => ({ default: {} }))
ctx.handler = (await import(MODULE_PATH)).default
})

View File

@@ -133,9 +133,6 @@ describe('ProjectListController', function () {
ctx.Features = {
hasFeature: sinon.stub(),
}
ctx.Metrics = {
inc: sinon.stub(),
}
ctx.SplitTestHandler = {
promises: {
getAssignment: sinon.stub().resolves({ variant: 'default' }),
@@ -208,10 +205,6 @@ describe('ProjectListController', function () {
default: ctx.settings,
}))
vi.doMock('@overleaf/metrics', () => ({
default: ctx.Metrics,
}))
vi.doMock(
'../../../../app/src/Features/SplitTests/SplitTestHandler',
() => ({

View File

@@ -22,12 +22,6 @@ describe('LearnedWordsManager', function () {
default: { db: ctx.db },
}))
vi.doMock('@overleaf/metrics', () => ({
default: {
inc: vi.fn(),
},
}))
vi.doMock('@overleaf/settings', () => ({
default: {
maxDictionarySize: 20,

View File

@@ -68,9 +68,6 @@ describe('SplitTestHandler', function () {
ctx.SessionManager = {
isUserLoggedIn: sinon.stub().returns(false),
}
ctx.Metrics = {
inc: sinon.stub(),
}
Features = {
hasFeature: vi.fn().mockReturnValue(true),
@@ -132,8 +129,6 @@ describe('SplitTestHandler', function () {
default: ctx.Settings,
}))
vi.doMock('@overleaf/metrics', () => ({ default: ctx.Metrics }))
ctx.SplitTestHandler = (await import(MODULE_PATH)).default
ctx.req = new MockRequest(vi)

View File

@@ -16,7 +16,6 @@ describe('SplitTestSessionHandler', function () {
get: sinon.stub().resolves(),
}
ctx.SplitTestUserGetter = {}
ctx.Metrics = {}
ctx.SplitTestCache.get = sinon.stub().resolves(
new Map(
@@ -67,10 +66,6 @@ describe('SplitTestSessionHandler', function () {
})
)
vi.doMock('@overleaf/metrics', () => ({
default: ctx.Metrics,
}))
vi.doMock('mongodb-legacy', () => ({
default: { ObjectId },
}))

View File

@@ -42,8 +42,6 @@ describe('TemplatesController', function () {
})
)
vi.doMock('@overleaf/metrics', () => ({ default: {} }))
ctx.TemplatesController = (await import(modulePath)).default
ctx.next = sinon.stub()
ctx.req = {

View File

@@ -58,9 +58,6 @@ describe('TpdsController', function () {
generateUniqueName: sinon.stub().resolves('unique'),
},
}
ctx.Metrics = {
inc: sinon.stub(),
}
vi.doMock(
'../../../../app/src/Features/ThirdPartyDataStore/TpdsUpdateHandler',
@@ -115,8 +112,6 @@ describe('TpdsController', function () {
})
)
vi.doMock('@overleaf/metrics', () => ({ default: ctx.Metrics }))
ctx.TpdsController = (await import(MODULE_PATH)).default
ctx.user_id = 'dsad29jlkjas'

View File

@@ -97,12 +97,6 @@ describe('TpdsUpdateSender', function () {
default: ctx.UserGetter,
}))
vi.doMock('@overleaf/metrics', () => ({
default: {
inc() {},
},
}))
ctx.TpdsUpdateSender = (await import(modulePath)).default
})

View File

@@ -41,10 +41,6 @@ describe('TokenAccessHandler', function () {
Project: (ctx.Project = {}),
}))
vi.doMock('@overleaf/metrics', () => ({
default: (ctx.Metrics = { inc: sinon.stub() }),
}))
vi.doMock('@overleaf/settings', () => ({
default: (ctx.settings = { disableLinkSharing: false }),
}))

View File

@@ -140,10 +140,6 @@ describe('UserController', function () {
},
}
ctx.Metrics = {
inc: sinon.stub(),
}
vi.doMock(
'../../../../app/src/Features/Analytics/AnalyticsManager',
() => ({
@@ -240,8 +236,6 @@ describe('UserController', function () {
default: ctx.Modules,
}))
vi.doMock('@overleaf/metrics', () => ({ default: ctx.Metrics }))
ctx.UserController = (await import(modulePath)).default
ctx.res = {

View File

@@ -35,7 +35,6 @@ describe('LockManager - releasing the lock', function () {
},
},
}))
vi.doMock('@overleaf/metrics', () => ({}))
vi.doMock('../../../../../app/src/infrastructure/RedisWrapper', () => ({
default: {
client() {

View File

@@ -45,13 +45,6 @@ describe('LockManager - getting the lock', function () {
},
}))
vi.doMock('@overleaf/metrics', () => ({
default: {
inc() {},
gauge() {},
},
}))
ctx.LockManager = (await import(modulePath)).default
ctx.callback = sinon.stub()

View File

@@ -45,12 +45,6 @@ describe('LockManager - trying the lock', function () {
},
}))
vi.doMock('@overleaf/metrics', () => ({
default: {
inc() {},
},
}))
ctx.LockManager = (await import(modulePath)).default
ctx.callback = sinon.stub()
ctx.key = 'lock:web:lockName:project-id}'