mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 14:43:46 +01:00
[web] avoid UserAnalyticsIdCache for split-test assignments (#34803)
* [web] split-tests: add helpers to using an already fetched mongo user * [web] split-tests: add ignoreOverrides option to getAssignment * [web] split-tests: reuse already fetched mongo users where possible * [web] notifications: fetch users in bulk for split-test assignment * [web] split-tests: use session for assignment where possible * [web] split-tests: pass req/res down into domain-capture hook * [web] split-tests: explicitly throw early * [web] split-tests: refine jsdoc types * [web] mark analyticsId and labsProgram as required in user model * [web] split-tests: refine jsdoc types * Revert "[web] split-tests: pass req/res down into domain-capture hook" This reverts commit 897a158384cd2b3e416fc34495b20d1e2a0e68f0. GitOrigin-RevId: ea4afd15c2506f369827b5193fd16d220d3c7d0e
This commit is contained in:
@@ -51,9 +51,11 @@ describe('ProjectHelper', function () {
|
||||
|
||||
ctx.SplitTestHandler = {
|
||||
promises: {
|
||||
getAssignmentForUser: vi.fn().mockResolvedValue({ variant: 'default' }),
|
||||
featureFlagEnabled: vi.fn().mockResolvedValue(false),
|
||||
},
|
||||
}
|
||||
ctx.req = {}
|
||||
ctx.res = {}
|
||||
|
||||
vi.doMock('mongodb-legacy', () => ({
|
||||
default: { ObjectId },
|
||||
@@ -159,7 +161,11 @@ describe('ProjectHelper', function () {
|
||||
|
||||
describe('getAllowedImagesForUser', function () {
|
||||
it('marks alpha only images as not allowed when the user is anonymous', async function (ctx) {
|
||||
const images = await ctx.ProjectHelper.getAllowedImagesForUser(null)
|
||||
const images = await ctx.ProjectHelper.getAllowedImagesForUser(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
null
|
||||
)
|
||||
const imageNames = _mapToAllowed(images)
|
||||
expect(imageNames).to.deep.equal([
|
||||
{ imageName: 'texlive-full:2018.1', allowed: true },
|
||||
@@ -170,7 +176,11 @@ describe('ProjectHelper', function () {
|
||||
})
|
||||
|
||||
it('marks monthly labs images as not allowed when the user is anonymous', async function (ctx) {
|
||||
const images = await ctx.ProjectHelper.getAllowedImagesForUser(null)
|
||||
const images = await ctx.ProjectHelper.getAllowedImagesForUser(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
null
|
||||
)
|
||||
const imageNames = _mapToAllowed(images)
|
||||
expect(imageNames).to.deep.equal([
|
||||
{ imageName: 'texlive-full:2018.1', allowed: true },
|
||||
@@ -181,10 +191,12 @@ describe('ProjectHelper', function () {
|
||||
})
|
||||
|
||||
it('marks monthly labs images as allowed when the user is enrolled', async function (ctx) {
|
||||
ctx.SplitTestHandler.promises.getAssignmentForUser.mockResolvedValue({
|
||||
variant: 'enabled',
|
||||
})
|
||||
const images = await ctx.ProjectHelper.getAllowedImagesForUser(ctx.user)
|
||||
ctx.SplitTestHandler.promises.featureFlagEnabled.mockResolvedValue(true)
|
||||
const images = await ctx.ProjectHelper.getAllowedImagesForUser(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
ctx.user
|
||||
)
|
||||
const imageNames = _mapToAllowed(images)
|
||||
expect(imageNames).to.deep.equal([
|
||||
{ imageName: 'texlive-full:2018.1', allowed: true },
|
||||
@@ -195,7 +207,11 @@ describe('ProjectHelper', function () {
|
||||
})
|
||||
|
||||
it('marks alpha only images as not allowed when when the user is not admin', async function (ctx) {
|
||||
const images = await ctx.ProjectHelper.getAllowedImagesForUser(ctx.user)
|
||||
const images = await ctx.ProjectHelper.getAllowedImagesForUser(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
ctx.user
|
||||
)
|
||||
const imageNames = _mapToAllowed(images)
|
||||
expect(imageNames).to.deep.equal([
|
||||
{ imageName: 'texlive-full:2018.1', allowed: true },
|
||||
@@ -207,6 +223,8 @@ describe('ProjectHelper', function () {
|
||||
|
||||
it('returns all images when the user is admin', async function (ctx) {
|
||||
const images = await ctx.ProjectHelper.getAllowedImagesForUser(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
ctx.adminUser
|
||||
)
|
||||
const imageNames = _mapToAllowed(images)
|
||||
|
||||
@@ -137,7 +137,7 @@ describe('ProjectListController', function () {
|
||||
ctx.SplitTestHandler = {
|
||||
promises: {
|
||||
getAssignment: sinon.stub().resolves({ variant: 'default' }),
|
||||
featureFlagEnabledForUser: sinon.stub().resolves(false),
|
||||
featureFlagEnabled: sinon.stub().resolves(false),
|
||||
hasUserBeenAssignedToVariant: sinon.stub().resolves(false),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ describe('SplitTestHandler', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.user = {
|
||||
_id: new ObjectId(),
|
||||
analyticsId: 'analytics-id',
|
||||
splitTests: {
|
||||
'active-test': [
|
||||
{
|
||||
@@ -256,9 +257,101 @@ describe('SplitTestHandler', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('mongo user variants', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.mongoUser = {
|
||||
_id: new ObjectId(),
|
||||
analyticsId: 'analytics-id',
|
||||
splitTests: {},
|
||||
}
|
||||
// a feature flag whose enabled variant covers 100% of users
|
||||
ctx.cachedSplitTests.set('my-flag', {
|
||||
name: 'my-flag',
|
||||
versions: [
|
||||
{
|
||||
active: true,
|
||||
analyticsEnabled: true,
|
||||
phase: 'release',
|
||||
versionNumber: 1,
|
||||
variants: [
|
||||
{
|
||||
name: 'enabled',
|
||||
rolloutPercent: 100,
|
||||
rolloutStripes: [{ start: 0, end: 100 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
describe('getActiveAssignmentsForMongoUser', function () {
|
||||
it('returns the assignments without re-fetching the user', async function (ctx) {
|
||||
const assignments =
|
||||
await ctx.SplitTestHandler.promises.getActiveAssignmentsForMongoUser(
|
||||
ctx.mongoUser
|
||||
)
|
||||
expect(assignments['active-test']).to.deep.equal({
|
||||
variantName: 'variant-1',
|
||||
phase: 'release',
|
||||
versionNumber: 2,
|
||||
})
|
||||
expect(ctx.SplitTestUserGetter.promises.getUser).to.not.have.been.called
|
||||
})
|
||||
|
||||
it('throws when _id is missing from the projection', async function (ctx) {
|
||||
await expect(
|
||||
ctx.SplitTestHandler.promises.getActiveAssignmentsForMongoUser({
|
||||
analyticsId: 'analytics-id',
|
||||
})
|
||||
).to.be.rejectedWith('bug: include db.users._id in projection')
|
||||
})
|
||||
|
||||
it('throws when analyticsId is missing from the projection', async function (ctx) {
|
||||
await expect(
|
||||
ctx.SplitTestHandler.promises.getActiveAssignmentsForMongoUser({
|
||||
_id: new ObjectId(),
|
||||
})
|
||||
).to.be.rejectedWith('bug: include db.users.analyticsId in projection')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAssignmentForMongoUser', function () {
|
||||
it('returns the assignment without re-fetching the user', async function (ctx) {
|
||||
const assignment =
|
||||
await ctx.SplitTestHandler.promises.getAssignmentForMongoUser(
|
||||
ctx.mongoUser,
|
||||
'my-flag'
|
||||
)
|
||||
expect(assignment.variant).to.equal('enabled')
|
||||
expect(ctx.SplitTestUserGetter.promises.getUser).to.not.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
describe('featureFlagEnabledForMongoUser', function () {
|
||||
it('returns true when the assigned variant is enabled', async function (ctx) {
|
||||
const enabled =
|
||||
await ctx.SplitTestHandler.promises.featureFlagEnabledForMongoUser(
|
||||
ctx.mongoUser,
|
||||
'my-flag'
|
||||
)
|
||||
expect(enabled).to.be.true
|
||||
})
|
||||
|
||||
it('returns false when the assigned variant is not enabled', async function (ctx) {
|
||||
const enabled =
|
||||
await ctx.SplitTestHandler.promises.featureFlagEnabledForMongoUser(
|
||||
ctx.mongoUser,
|
||||
'active-test'
|
||||
)
|
||||
expect(enabled).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a user without assignments', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.user = { _id: new ObjectId() }
|
||||
ctx.user = { _id: new ObjectId(), analyticsId: 'analytics-id' }
|
||||
ctx.SplitTestUserGetter.promises.getUser.resolves(ctx.user)
|
||||
ctx.assignments =
|
||||
await ctx.SplitTestHandler.promises.getActiveAssignmentsForUser(
|
||||
@@ -421,6 +514,35 @@ describe('SplitTestHandler', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAssignment query overrides', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.AnalyticsManager.getIdsFromSession.returns({
|
||||
userId: 'abc123abc123',
|
||||
})
|
||||
// 'active-test' would compute to 'variant-1'; override it to 'default'
|
||||
ctx.req.query = { 'active-test': 'default' }
|
||||
})
|
||||
|
||||
it('applies a query-string override by default', async function (ctx) {
|
||||
const { variant } = await ctx.SplitTestHandler.promises.getAssignment(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
'active-test'
|
||||
)
|
||||
expect(variant).to.equal('default')
|
||||
})
|
||||
|
||||
it('ignores query-string overrides when ignoreOverrides is set', async function (ctx) {
|
||||
const { variant } = await ctx.SplitTestHandler.promises.getAssignment(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
'active-test',
|
||||
{ ignoreOverrides: true }
|
||||
)
|
||||
expect(variant).to.equal('variant-1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('variant user limits', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.AnalyticsManager.getIdsFromSession.returns({
|
||||
|
||||
@@ -163,7 +163,7 @@ describe('FeaturesUpdater', function () {
|
||||
|
||||
ctx.SplitTestHandler = {
|
||||
promises: {
|
||||
featureFlagEnabledForUser: sinon.stub().resolves(false),
|
||||
featureFlagEnabledForMongoUser: sinon.stub().resolves(false),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('AiFeatureUsageRateLimiter', function () {
|
||||
|
||||
ctx.SplitTestHandler = {
|
||||
promises: {
|
||||
featureFlagEnabledForUser: sinon.stub().resolves(true),
|
||||
featureFlagEnabledForMongoUser: sinon.stub().resolves(true),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('WorkbenchRateLimiter', function () {
|
||||
ctx.SplitTestHandler = {
|
||||
promises: {
|
||||
getAssignmentForUser: sinon.stub(),
|
||||
featureFlagEnabledForUser: sinon.stub().resolves(true),
|
||||
featureFlagEnabledForMongoUser: sinon.stub().resolves(true),
|
||||
},
|
||||
}
|
||||
ctx.SplitTestHandler.promises.getAssignmentForUser
|
||||
|
||||
Reference in New Issue
Block a user