Merge pull request #17907 from overleaf/ab-split-test-assignments-optim-pt1

[web] Read anonymous split test assignments in session from both old&new fields

GitOrigin-RevId: 5235bb3e7d72d5ff9e89c6543b70fb80e9f1213c
This commit is contained in:
Alexandre Bourdin
2024-04-19 08:04:11 +00:00
committed by Copybot
parent 945e51b8ed
commit bee4c95c28
11 changed files with 621 additions and 204 deletions
@@ -23,14 +23,14 @@ describe('BetaProgramController', function () {
user: this.user,
},
}
this.SplitTestHandler = {
this.SplitTestSessionHandler = {
promises: {
sessionMaintenance: sinon.stub(),
},
}
this.BetaProgramController = SandboxedModule.require(modulePath, {
requires: {
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
'../SplitTests/SplitTestSessionHandler': this.SplitTestSessionHandler,
'./BetaProgramHandler': (this.BetaProgramHandler = {
promises: {
optIn: sinon.stub().resolves(),
@@ -76,7 +76,7 @@ describe('BetaProgramController', function () {
it('should invoke the session maintenance', function (done) {
this.res.callback = () => {
this.SplitTestHandler.promises.sessionMaintenance.should.have.been.calledWith(
this.SplitTestSessionHandler.promises.sessionMaintenance.should.have.been.calledWith(
this.req
)
done()
@@ -130,7 +130,7 @@ describe('BetaProgramController', function () {
it('should invoke the session maintenance', function (done) {
this.res.callback = () => {
this.SplitTestHandler.promises.sessionMaintenance.should.have.been.calledWith(
this.SplitTestSessionHandler.promises.sessionMaintenance.should.have.been.calledWith(
this.req,
null
)
@@ -136,6 +136,8 @@ describe('ProjectController', function () {
getAssignment: sinon.stub().resolves({ variant: 'default' }),
},
getAssignment: sinon.stub().yields(null, { variant: 'default' }),
}
this.SplitTestSessionHandler = {
sessionMaintenance: sinon.stub().yields(),
}
this.InstitutionsFeatures = {
@@ -160,6 +162,7 @@ describe('ProjectController', function () {
'@overleaf/settings': this.settings,
'@overleaf/metrics': this.Metrics,
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
'../SplitTests/SplitTestSessionHandler': this.SplitTestSessionHandler,
'./ProjectDeleter': this.ProjectDeleter,
'./ProjectDuplicator': this.ProjectDuplicator,
'./ProjectCreationHandler': this.ProjectCreationHandler,
@@ -536,7 +539,7 @@ describe('ProjectController', function () {
it('should invoke the session maintenance for logged in user', function (done) {
this.res.render = () => {
this.SplitTestHandler.sessionMaintenance.should.have.been.calledWith(
this.SplitTestSessionHandler.sessionMaintenance.should.have.been.calledWith(
this.req,
this.user
)
@@ -548,7 +551,7 @@ describe('ProjectController', function () {
it('should invoke the session maintenance for anonymous user', function (done) {
this.SessionManager.getLoggedInUserId.returns(null)
this.res.render = () => {
this.SplitTestHandler.sessionMaintenance.should.have.been.calledWith(
this.SplitTestSessionHandler.sessionMaintenance.should.have.been.calledWith(
this.req
)
done()
@@ -102,10 +102,14 @@ describe('ProjectListController', function () {
}
this.SplitTestHandler = {
promises: {
sessionMaintenance: sinon.stub().resolves(),
getAssignment: sinon.stub().resolves({ variant: 'default' }),
},
}
this.SplitTestSessionHandler = {
promises: {
sessionMaintenance: sinon.stub().resolves(),
},
}
this.SubscriptionViewModelBuilder = {
promises: {
getBestSubscription: sinon.stub().resolves({ type: 'free' }),
@@ -141,6 +145,7 @@ describe('ProjectListController', function () {
'@overleaf/settings': this.settings,
'@overleaf/metrics': this.Metrics,
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
'../SplitTests/SplitTestSessionHandler': this.SplitTestSessionHandler,
'../User/UserController': this.UserController,
'./ProjectHelper': this.ProjectHelper,
'../Subscription/LimitationsManager': this.LimitationsManager,
@@ -223,7 +228,7 @@ describe('ProjectListController', function () {
it('should invoke the session maintenance', function (done) {
this.Features.hasFeature.withArgs('saas').returns(true)
this.res.render = () => {
this.SplitTestHandler.promises.sessionMaintenance.should.have.been.calledWith(
this.SplitTestSessionHandler.promises.sessionMaintenance.should.have.been.calledWith(
this.req,
this.user
)
@@ -27,12 +27,6 @@ describe('SplitTestHandler', function () {
this.cachedSplitTests.set(splitTest.name, splitTest)
}
this.UserGetter = {
promises: {
getUser: sinon.stub().resolves(null),
},
}
this.SplitTest = {
find: sinon.stub().returns({
exec: sinon.stub().resolves(this.splitTests),
@@ -54,11 +48,20 @@ describe('SplitTestHandler', function () {
}
this.AnalyticsManager = {
getIdsFromSession: sinon.stub(),
setUserPropertyForAnalyticsId: sinon.stub(),
}
this.LocalsHelper = {
setSplitTestVariant: sinon.stub(),
setSplitTestInfo: sinon.stub(),
}
this.SplitTestSessionHandler = {
collectSessionStats: sinon.stub(),
}
this.SplitTestUserGetter = {
promises: {
getUser: sinon.stub().resolves(null),
},
}
this.SplitTestHandler = SandboxedModule.require(MODULE_PATH, {
requires: {
@@ -68,6 +71,8 @@ describe('SplitTestHandler', function () {
'../User/UserUpdater': {},
'../Analytics/AnalyticsManager': this.AnalyticsManager,
'./LocalsHelper': this.LocalsHelper,
'./SplitTestSessionHandler': this.SplitTestSessionHandler,
'./SplitTestUserGetter': this.SplitTestUserGetter,
'@overleaf/settings': this.Settings,
},
})
@@ -100,9 +105,7 @@ describe('SplitTestHandler', function () {
],
},
}
this.UserGetter.promises.getUser
.withArgs(this.user._id)
.resolves(this.user)
this.SplitTestUserGetter.promises.getUser.resolves(this.user)
this.assignments =
await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
this.user._id
@@ -164,9 +167,7 @@ describe('SplitTestHandler', function () {
describe('with a user without assignments', function () {
beforeEach(async function () {
this.user = { _id: new ObjectId() }
this.UserGetter.promises.getUser
.withArgs(this.user._id)
.resolves(this.user)
this.SplitTestUserGetter.promises.getUser.resolves(this.user)
this.assignments =
await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
this.user._id
@@ -0,0 +1,241 @@
const Path = require('path')
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { expect } = require('chai')
const MODULE_PATH = Path.join(
__dirname,
'../../../../app/src/Features/SplitTests/SplitTestSessionHandler'
)
describe('SplitTestSessionHandler', function () {
beforeEach(function () {
this.SplitTestCache = {
get: sinon.stub().resolves(),
}
this.SplitTestUserGetter = {}
this.Metrics = {}
this.SplitTestSessionHandler = SandboxedModule.require(MODULE_PATH, {
requires: {
'./SplitTestCache': this.SplitTestCache,
'./SplitTestUserGetter': this.SplitTestUserGetter,
'@overleaf/metrics': this.Metrics,
},
})
})
it('should read from the splitTests field', async function () {
const session = {
splitTests: {
'anon-test-1': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712872800000), // 2024-04-11 22:00:00
},
],
'anon-test-2': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712307600000), // 2024-04-05 09:00:00
},
{
variantName: 'v-2',
versionNumber: 2,
phase: 'release',
assignedAt: new Date(1712581200000), // 2024-04-08 13:00:00
},
],
},
sta: ``,
}
const assignments =
await this.SplitTestSessionHandler.promises.getAssignments(session)
expect(assignments).to.deep.equal({
'anon-test-1': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712872800000),
},
],
'anon-test-2': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712307600000),
},
{
variantName: 'v-2',
versionNumber: 2,
phase: 'release',
assignedAt: new Date(1712581200000),
},
],
})
})
it('should not read from the sta field', async function () {
this.SplitTestCache.get = sinon.stub().resolves(
new Map(
Object.entries({
'anon-test-1': {
_id: '661f92a4669764bb03f73e37',
name: 'anon-test-1',
versions: [
{
versionNumber: 1,
variants: [
{
name: 'enabled',
},
],
},
],
},
'anon-test-2': {
_id: '661f92a9d68ea711d6bf2df4',
name: 'anon-test-2',
versions: [
{
versionNumber: 1,
variants: [
{
name: 'v-1',
},
{
name: 'v-2',
},
],
},
],
},
})
)
)
const session = {
sta: `Zh+SpGaXZLsD9z43_1=d:sbrvs0;Zh+SqdaOpxHWvy30_1=d:sbtqg0;Zh+SqdaOpxHWvy30_2=1:sbsi00`,
}
const assignments =
await this.SplitTestSessionHandler.promises.getAssignments(session)
expect(assignments).to.deep.equal({
'anon-test-1': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712829600000),
},
],
'anon-test-2': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712916000000),
},
{
variantName: 'v-2',
versionNumber: 2,
phase: 'release',
assignedAt: new Date(1712858400000),
},
],
})
})
it('should merge assignments from both splitTests and sta fields', async function () {
this.SplitTestCache.get = sinon.stub().resolves(
new Map(
Object.entries({
'anon-test-1': {
_id: '661f92a4669764bb03f73e37',
name: 'anon-test-1',
versions: [
{
versionNumber: 1,
variants: [
{
name: 'enabled',
},
],
},
],
},
'anon-test-2': {
_id: '661f92a9d68ea711d6bf2df4',
name: 'anon-test-2',
versions: [
{
versionNumber: 1,
variants: [
{
name: 'v-1',
},
{
name: 'v-2',
},
],
},
],
},
})
)
)
const session = {
splitTests: {
'anon-test-1': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712872800000),
},
],
'anon-test-2': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712307600000),
},
],
},
sta: `Zh+SqdaOpxHWvy30_2=1:sbsi00`,
}
const assignments =
await this.SplitTestSessionHandler.promises.getAssignments(session)
expect(assignments).to.deep.equal({
'anon-test-1': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712872800000),
},
],
'anon-test-2': [
{
variantName: 'default',
versionNumber: 1,
phase: 'release',
assignedAt: new Date(1712307600000),
},
{
variantName: 'v-2',
versionNumber: 2,
phase: 'release',
assignedAt: new Date(1712858400000),
},
],
})
})
})