Skip to content

Commit 9f56e30

Browse files
committed
feature test
1 parent 83fa2b0 commit 9f56e30

File tree

3 files changed

+197
-33
lines changed

3 files changed

+197
-33
lines changed

lib/core/decision_service/index.spec.ts

Lines changed: 195 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import OptimizelyUserContext from '../../optimizely_user_context';
2020
import { bucket } from '../bucketer';
2121
import { getTestProjectConfig, getTestProjectConfigWithFeatures } from '../../tests/test_data';
2222
import { createProjectConfig, ProjectConfig } from '../../project_config/project_config';
23-
import { BucketerParams, Experiment } from '../../shared_types';
23+
import { BucketerParams, Experiment, UserProfile } from '../../shared_types';
2424
import { CONTROL_ATTRIBUTES, DECISION_SOURCES } from '../../utils/enums';
2525
import { getDecisionTestDatafile } from '../../tests/decision_test_datafile';
2626

@@ -688,6 +688,10 @@ describe('DecisionService', () => {
688688
});
689689

690690
describe('getVariationForFeature', () => {
691+
beforeEach(() => {
692+
mockBucket.mockReset();
693+
});
694+
691695
it('should return variation from the first experiment for which a variation is available', () => {
692696
const { decisionService } = getDecisionService();
693697

@@ -737,12 +741,95 @@ describe('DecisionService', () => {
737741
config, config.experimentKeyMap['exp_2'], user, false, expect.anything());
738742
});
739743

740-
describe('when no variation is found for any experiment and a target delivery audience condition is satisfied', () => {
744+
it('should save the variation found for an experiment in the user profile without', () => {
745+
const { decisionService, userProfileService } = getDecisionService({ userProfileService: true });
746+
747+
const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation')
748+
.mockImplementation((
749+
config,
750+
experiment: any,
751+
user,
752+
shouldIgnoreUPS,
753+
userProfileTracker: any,
754+
) => {
755+
if (experiment.key === 'exp_2') {
756+
const variation = 'variation_2';
757+
758+
userProfileTracker.userProfile[experiment.id] = {
759+
variation_id: '5002',
760+
};
761+
userProfileTracker.isProfileUpdated = true;
762+
763+
return {
764+
result: 'variation_2',
765+
reasons: [],
766+
};
767+
}
768+
return {
769+
result: null,
770+
reasons: [],
771+
}
772+
});
773+
774+
const config = createProjectConfig(getDecisionTestDatafile());
775+
776+
const user = new OptimizelyUserContext({
777+
optimizely: {} as any,
778+
userId: 'tester',
779+
attributes: {
780+
age: 40,
781+
},
782+
});
783+
784+
userProfileService?.lookup.mockImplementation((userId: string) => {
785+
if (userId === 'tester') {
786+
return {
787+
user_id: 'tester',
788+
experiment_bucket_map: {
789+
'2001': {
790+
variation_id: '5001',
791+
},
792+
},
793+
};
794+
}
795+
return null;
796+
});
797+
798+
799+
const feature = config.featureKeyMap['flag_1'];
800+
const variation = decisionService.getVariationForFeature(config, feature, user);
801+
802+
expect(variation.result).toEqual({
803+
experiment: config.experimentKeyMap['exp_2'],
804+
variation: config.variationIdMap['5002'],
805+
decisionSource: DECISION_SOURCES.FEATURE_TEST,
806+
});
807+
808+
expect(userProfileService?.lookup).toHaveBeenCalledTimes(1);
809+
expect(userProfileService?.lookup).toHaveBeenCalledWith('tester');
810+
811+
expect(userProfileService?.save).toHaveBeenCalledTimes(1);
812+
expect(userProfileService?.save).toHaveBeenCalledWith({
813+
user_id: 'tester',
814+
experiment_bucket_map: {
815+
'2001': {
816+
variation_id: '5001',
817+
},
818+
'2002': {
819+
variation_id: '5002',
820+
},
821+
},
822+
});
823+
});
824+
825+
describe('when no variation is found for any experiment and a targeted delivery \
826+
audience condition is satisfied', () => {
741827
beforeEach(() => {
742828
mockBucket.mockReset();
743829
});
744830

745-
it('should return variation from the target delivery for which audience condition is satisfied if the user is bucketed into it', () => {
831+
it('should return variation from the target delivery for which audience condition \
832+
is satisfied if the user is bucketed into it', () => {
746833
const { decisionService } = getDecisionService();
747834

748835
const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation')
@@ -757,7 +844,7 @@ describe('DecisionService', () => {
757844
optimizely: {} as any,
758845
userId: 'tester',
759846
attributes: {
760-
age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2
847+
age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2 and delivery_3
761848
},
762849
});
763850

@@ -796,8 +883,8 @@ describe('DecisionService', () => {
796883
verifyBucketCall(0, config, config.experimentIdMap['3002'], user);
797884
});
798885

799-
it('should skip to everyone else targeting rule if the user is not bucketed into the targeted delivery for which \
800-
audience condition is satisfied', () => {
886+
it('should skip to everyone else targeting rule if the user is not bucketed \
887+
into the targeted delivery for which audience condition is satisfied', () => {
801888
const { decisionService } = getDecisionService();
802889

803890
const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation')
@@ -812,7 +899,7 @@ describe('DecisionService', () => {
812899
optimizely: {} as any,
813900
userId: 'tester',
814901
attributes: {
815-
age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2
902+
age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2 and delivery_3
816903
},
817904
});
818905

@@ -852,5 +939,106 @@ describe('DecisionService', () => {
852939
verifyBucketCall(1, config, config.experimentIdMap['default-rollout-id'], user);
853940
});
854941
});
942+
943+
it('should return variation from the everyone else targeting rule if no variation \
944+
is found for any experiment or targeted delivery', () => {
945+
const { decisionService } = getDecisionService();
946+
947+
const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation')
948+
.mockReturnValue({
949+
result: null,
950+
reasons: [],
951+
});
952+
953+
const config = createProjectConfig(getDecisionTestDatafile());
954+
955+
const user = new OptimizelyUserContext({
956+
optimizely: {} as any,
957+
userId: 'tester',
958+
attributes: {
959+
age: 100, // this should not satisfy any audience condition for any targeted delivery
960+
},
961+
});
962+
963+
mockBucket.mockImplementation((param: BucketerParams) => {
964+
const ruleKey = param.experimentKey;
965+
console.log('bucket called for ' + ruleKey);
966+
if (ruleKey === 'default-rollout-key') {
967+
return {
968+
result: '5007',
969+
reasons: [],
970+
};
971+
}
972+
return {
973+
result: null,
974+
reasons: [],
975+
};
976+
});
977+
978+
const feature = config.featureKeyMap['flag_1'];
979+
const variation = decisionService.getVariationForFeature(config, feature, user);
980+
981+
expect(variation.result).toEqual({
982+
experiment: config.experimentIdMap['default-rollout-id'],
983+
variation: config.variationIdMap['5007'],
984+
decisionSource: DECISION_SOURCES.ROLLOUT,
985+
});
986+
987+
expect(resolveVariationSpy).toHaveBeenCalledTimes(3);
988+
expect(resolveVariationSpy).toHaveBeenNthCalledWith(1,
989+
config, config.experimentKeyMap['exp_1'], user, false, expect.anything());
990+
expect(resolveVariationSpy).toHaveBeenNthCalledWith(2,
991+
config, config.experimentKeyMap['exp_2'], user, false, expect.anything());
992+
expect(resolveVariationSpy).toHaveBeenNthCalledWith(3,
993+
config, config.experimentKeyMap['exp_3'], user, false, expect.anything());
994+
995+
expect(mockBucket).toHaveBeenCalledTimes(1);
996+
verifyBucketCall(0, config, config.experimentIdMap['default-rollout-id'], user);
997+
});
998+
999+
it('should return null if no variation is found for any experiment, targeted delivery, or everyone else targeting rule', () => {
1000+
const { decisionService } = getDecisionService();
1001+
1002+
const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation')
1003+
.mockReturnValue({
1004+
result: null,
1005+
reasons: [],
1006+
});
1007+
1008+
const config = createProjectConfig(getDecisionTestDatafile());
1009+
const rolloutId = config.featureKeyMap['flag_1'].rolloutId;
1010+
config.rolloutIdMap[rolloutId].experiments = []; // remove the experiments from the rollout
1011+
1012+
const user = new OptimizelyUserContext({
1013+
optimizely: {} as any,
1014+
userId: 'tester',
1015+
attributes: {
1016+
age: 10,
1017+
},
1018+
});
1019+
1020+
const feature = config.featureKeyMap['flag_1'];
1021+
const variation = decisionService.getVariationForFeature(config, feature, user);
1022+
1023+
expect(variation.result).toEqual({
1024+
experiment: null,
1025+
variation: null,
1026+
decisionSource: DECISION_SOURCES.ROLLOUT,
1027+
});
1028+
1029+
expect(resolveVariationSpy).toHaveBeenCalledTimes(3);
1030+
expect(resolveVariationSpy).toHaveBeenNthCalledWith(1,
1031+
config, config.experimentKeyMap['exp_1'], user, false, expect.anything());
1032+
expect(resolveVariationSpy).toHaveBeenNthCalledWith(2,
1033+
config, config.experimentKeyMap['exp_2'], user, false, expect.anything());
1034+
expect(resolveVariationSpy).toHaveBeenNthCalledWith(3,
1035+
config, config.experimentKeyMap['exp_3'], user, false, expect.anything());
1036+
1037+
expect(mockBucket).toHaveBeenCalledTimes(0);
1038+
});
8551039
});
1040+
1041+
// describe('getVariationsForFeatureList', () => {
1042+
1043+
// });
8561044
});

lib/core/decision_service/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -655,10 +655,10 @@ export class DecisionService {
655655
}
656656

657657
if(!shouldIgnoreUPS) {
658-
this.saveUserProfile(userId, userProfileTracker)
658+
this.saveUserProfile(userId, userProfileTracker);
659659
}
660660

661-
return decisions
661+
return decisions;
662662

663663
}
664664

lib/tests/decision_test_datafile.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,6 @@ const testDatafile = {
7878
value: 60
7979
}
8080
]
81-
],
82-
[
83-
"or",
84-
[
85-
"or",
86-
{
87-
"match": "gt",
88-
name: "age",
89-
"type": "custom_attribute",
90-
value: 22
91-
}
92-
]
9381
]
9482
],
9583
id: "4002"
@@ -109,18 +97,6 @@ const testDatafile = {
10997
value: 90
11098
}
11199
]
112-
],
113-
[
114-
"or",
115-
[
116-
"or",
117-
{
118-
"match": "gt",
119-
name: "age",
120-
"type": "custom_attribute",
121-
value: 60
122-
}
123-
]
124100
]
125101
],
126102
id: "4003"

0 commit comments

Comments
 (0)