Skip to content

Commit d966df0

Browse files
committed
all tests
1 parent ba3165c commit d966df0

File tree

5 files changed

+130
-4
lines changed

5 files changed

+130
-4
lines changed

lib/event_processor/event_builder/log_event.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type Metadata = {
7272
rule_type: string;
7373
variation_key: string;
7474
enabled: boolean;
75+
cmab_uuid?: string;
7576
}
7677

7778
export type SnapshotEvent = {
@@ -156,7 +157,7 @@ function makeConversionSnapshot(conversion: ConversionEvent): Snapshot {
156157
}
157158

158159
function makeDecisionSnapshot(event: ImpressionEvent): Snapshot {
159-
const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled } = event
160+
const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled, cmabUuid } = event
160161
const layerId = layer ? layer.id : null
161162
const experimentId = experiment?.id ?? ''
162163
const variationId = variation?.id ?? ''
@@ -174,6 +175,7 @@ function makeDecisionSnapshot(event: ImpressionEvent): Snapshot {
174175
rule_type: ruleType,
175176
variation_key: variationKey,
176177
enabled: enabled,
178+
cmab_uuid: cmabUuid,
177179
},
178180
},
179181
],

lib/event_processor/event_builder/user_event.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export type ImpressionEvent = BaseUserEvent & {
7676
flagKey: string;
7777
ruleType: string;
7878
enabled: boolean;
79+
cmabUuid?: string;
7980
};
8081

8182
export type EventTags = {
@@ -144,6 +145,7 @@ export const buildImpressionEvent = function({
144145
const experimentId = decision.getExperimentId(decisionObj);
145146
const variationKey = decision.getVariationKey(decisionObj);
146147
const variationId = decision.getVariationId(decisionObj);
148+
const cmabUuid = decisionObj.cmabUuid;
147149

148150
const layerId = experimentId !== null ? getLayerId(configObj, experimentId) : null;
149151

@@ -185,6 +187,7 @@ export const buildImpressionEvent = function({
185187
flagKey: flagKey,
186188
ruleType: ruleType,
187189
enabled: enabled,
190+
cmabUuid,
188191
};
189192
};
190193

lib/optimizely/index.spec.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ import { createProjectConfig } from '../project_config/project_config';
2525
import { getMockLogger } from '../tests/mock/mock_logger';
2626
import { createOdpManager } from '../odp/odp_manager_factory.node';
2727
import { extractOdpManager } from '../odp/odp_manager_factory';
28+
import { Value } from '../utils/promise/operation_value';
29+
import { getDecisionTestDatafile } from '../tests/decision_test_datafile';
30+
import { DECISION_SOURCES } from '../utils/enums';
31+
import OptimizelyUserContext from '../optimizely_user_context';
32+
import { newErrorDecision } from '../optimizely_decision';
33+
import { EventDispatcher } from '../shared_types';
34+
import { ImpressionEvent } from '../event_processor/event_builder/user_event';
2835

2936
describe('Optimizely', () => {
3037
const eventDispatcher = {
@@ -59,4 +66,112 @@ describe('Optimizely', () => {
5966
expect(eventProcessor.makeDisposable).toHaveBeenCalled();
6067
expect(odpManager.makeDisposable).toHaveBeenCalled();
6168
});
69+
70+
describe('decideAsync', () => {
71+
it('should return an error decision with correct reasons if decisionService returns error', async () => {
72+
const projectConfig = createProjectConfig(getDecisionTestDatafile());
73+
74+
const projectConfigManager = getMockProjectConfigManager({
75+
initConfig: projectConfig,
76+
});
77+
78+
const optimizely = new Optimizely({
79+
clientEngine: 'node-sdk',
80+
projectConfigManager,
81+
jsonSchemaValidator,
82+
logger,
83+
eventProcessor,
84+
odpManager,
85+
disposable: true,
86+
cmabService: {} as any
87+
});
88+
89+
// @ts-ignore
90+
const decisionService = optimizely.decisionService;
91+
vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => {
92+
return Value.of('async', [{
93+
error: true,
94+
result: {
95+
variation: null,
96+
experiment: projectConfig.experimentKeyMap['exp_3'],
97+
decisionSource: DECISION_SOURCES.FEATURE_TEST,
98+
},
99+
reasons:[
100+
['test reason %s', '1'],
101+
['test reason %s', '2'],
102+
]
103+
}]);
104+
});
105+
106+
const user = new OptimizelyUserContext({
107+
optimizely: {} as any,
108+
userId: 'tester',
109+
attributes: {
110+
country: 'BD',
111+
age: 80, // should satisfy audience condition for exp_3 which is cmab and not others
112+
},
113+
});
114+
115+
const decision = await optimizely.decideAsync(user, 'flag_1', []);
116+
117+
expect(decision).toEqual(newErrorDecision('flag_1', user, ['test reason 1', 'test reason 2']));
118+
});
119+
120+
it.only('should include cmab uuid in dispatched event if decisionService returns a cmab uuid', async () => {
121+
const projectConfig = createProjectConfig(getDecisionTestDatafile());
122+
123+
const projectConfigManager = getMockProjectConfigManager({
124+
initConfig: projectConfig,
125+
});
126+
127+
const eventProcessor = getForwardingEventProcessor(eventDispatcher);
128+
const processSpy = vi.spyOn(eventProcessor, 'process');
129+
130+
const optimizely = new Optimizely({
131+
clientEngine: 'node-sdk',
132+
projectConfigManager,
133+
eventProcessor,
134+
jsonSchemaValidator,
135+
logger,
136+
odpManager,
137+
disposable: true,
138+
cmabService: {} as any
139+
});
140+
141+
// @ts-ignore
142+
const decisionService = optimizely.decisionService;
143+
vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => {
144+
return Value.of('async', [{
145+
error: false,
146+
result: {
147+
cmabUuid: 'uuid-cmab',
148+
variation: projectConfig.variationIdMap['5003'],
149+
experiment: projectConfig.experimentKeyMap['exp_3'],
150+
decisionSource: DECISION_SOURCES.FEATURE_TEST,
151+
},
152+
reasons: [],
153+
}]);
154+
});
155+
156+
const user = new OptimizelyUserContext({
157+
optimizely: {} as any,
158+
userId: 'tester',
159+
attributes: {
160+
country: 'BD',
161+
age: 80, // should satisfy audience condition for exp_3 which is cmab and not others
162+
},
163+
});
164+
165+
const decision = await optimizely.decideAsync(user, 'flag_1', []);
166+
167+
expect(decision.ruleKey).toBe('exp_3');
168+
expect(decision.flagKey).toBe('flag_1');
169+
expect(decision.variationKey).toBe('variation_3');
170+
expect(decision.enabled).toBe(true);
171+
172+
expect(eventProcessor.process).toHaveBeenCalledOnce();
173+
const event = processSpy.mock.calls[0][0] as ImpressionEvent;
174+
expect(event.cmabUuid).toBe('uuid-cmab');
175+
});
176+
});
62177
});

lib/optimizely/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,8 +1691,7 @@ export default class Optimizely extends BaseService implements Client {
16911691
// flagDecisions[key] = decision.result;
16921692
// decisionReasonsMap[key] = [...decisionReasonsMap[key], ...decision.reasons];
16931693
if(decision.error) {
1694-
decisionMap[key] = newErrorDecision(key, user, decision.reasons.map(r => r[0]));
1695-
decisionReasonsMap[key] = decision.reasons;
1694+
decisionMap[key] = newErrorDecision(key, user, decision.reasons.map(r => sprintf(r[0], ...r.slice(1))));
16961695
} else {
16971696
flagDecisions[key] = decision.result;
16981697
decisionReasonsMap[key] = decision.reasons;
@@ -1701,6 +1700,13 @@ export default class Optimizely extends BaseService implements Client {
17011700

17021701
for(const validFlag of validFlags) {
17031702
const validKey = validFlag.key;
1703+
1704+
// if there is already a value for this flag, that must have come from
1705+
// the newErrorDecision above, so we skip it
1706+
if (decisionMap[validKey]) {
1707+
continue;
1708+
}
1709+
17041710
const decision = this.generateDecision(user, validKey, flagDecisions[validKey], decisionReasonsMap[validKey], allDecideOptions, configObj);
17051711

17061712
if(!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || decision.enabled) {

vitest.config.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default defineConfig({
2626
test: {
2727
onConsoleLog: () => true,
2828
environment: 'happy-dom',
29-
include: ['**/decision_service/index.spec.ts'],
29+
include: ['**/*.spec.ts'],
3030
typecheck: {
3131
enabled: true,
3232
tsconfig: 'tsconfig.spec.json',

0 commit comments

Comments
 (0)