Skip to content

Commit 9817c0e

Browse files
committed
feat: added-unit-tests
1 parent 55de0f6 commit 9817c0e

File tree

2 files changed

+294
-12
lines changed

2 files changed

+294
-12
lines changed

flagsmith-engine/index.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ export { IdentityModel } from './identities/models.js';
99
export { TraitModel } from './identities/traits/models.js';
1010
export { SegmentModel } from './segments/models.js';
1111

12-
type segmentOverride = {
12+
type SegmentOverride = {
1313
feature: FeatureContext;
1414
segmentName: string;
1515
};
1616

17+
export type SegmentOverrides = Record<string, SegmentOverride>;
18+
1719
/**
1820
* Evaluates flags and segments for the given context.
1921
*
@@ -40,9 +42,9 @@ export function getEvaluationResult(context: EvaluationContext): EvaluationResul
4042
* @param context - EvaluationContext containing identity and segment definitions
4143
* @returns Object containing segments the identity belongs to and any feature overrides
4244
*/
43-
function evaluateSegments(context: EvaluationContext): {
45+
export function evaluateSegments(context: EvaluationContext): {
4446
segments: EvaluationResult['segments'];
45-
segmentOverrides: Record<string, segmentOverride>;
47+
segmentOverrides: Record<string, SegmentOverride>;
4648
} {
4749
if (!context.identity || !context.segments) {
4850
return { segments: [], segmentOverrides: {} };
@@ -67,8 +69,8 @@ function evaluateSegments(context: EvaluationContext): {
6769
* @param identitySegments - Segments that the identity belongs to
6870
* @returns Map of feature keys to their highest-priority segment overrides
6971
*/
70-
function processSegmentOverrides(identitySegments: any[]): Record<string, segmentOverride> {
71-
const segmentOverrides: Record<string, segmentOverride> = {};
72+
export function processSegmentOverrides(identitySegments: any[]): Record<string, SegmentOverride> {
73+
const segmentOverrides: Record<string, SegmentOverride> = {};
7274

7375
for (const segment of identitySegments) {
7476
if (!segment.overrides) continue;
@@ -100,9 +102,9 @@ function processSegmentOverrides(identitySegments: any[]): Record<string, segmen
100102
* @param segmentOverrides - Map of feature keys to their segment overrides
101103
* @returns EvaluationResultFlags containing evaluated flag results
102104
*/
103-
function evaluateFeatures(
105+
export function evaluateFeatures(
104106
context: EvaluationContext,
105-
segmentOverrides: Record<string, segmentOverride>
107+
segmentOverrides: Record<string, SegmentOverride>
106108
): EvaluationResultFlags {
107109
const flags: EvaluationResultFlags = [];
108110

@@ -126,21 +128,24 @@ function evaluateFeatures(
126128
return flags;
127129
}
128130

129-
function shouldApplyOverride(
131+
export function shouldApplyOverride(
130132
override: any,
131-
existingOverrides: Record<string, segmentOverride>
133+
existingOverrides: Record<string, SegmentOverride>
132134
): boolean {
133135
const currentOverride = existingOverrides[override.feature_key];
134136
return (
135137
!currentOverride || isHigherPriority(override.priority, currentOverride.feature.priority)
136138
);
137139
}
138140

139-
function isHigherPriority(priorityA: number | undefined, priorityB: number | undefined): boolean {
141+
export function isHigherPriority(
142+
priorityA: number | undefined,
143+
priorityB: number | undefined
144+
): boolean {
140145
return (priorityA ?? Infinity) < (priorityB ?? Infinity);
141146
}
142147

143-
const getTargetingMatchReason = (segmentOverride: segmentOverride) => {
148+
const getTargetingMatchReason = (segmentOverride: SegmentOverride) => {
144149
if (segmentOverride) {
145150
return segmentOverride.segmentName === IDENTITY_OVERRIDE_SEGMENT_NAME
146151
? TARGETING_REASONS.IDENTITY_OVERRIDE

tests/engine/unit/engine.test.ts

Lines changed: 278 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { getEvaluationResult } from '../../../flagsmith-engine/index.js';
1+
import {
2+
evaluateFeatures,
3+
evaluateSegments,
4+
getEvaluationResult,
5+
isHigherPriority,
6+
SegmentOverrides,
7+
shouldApplyOverride
8+
} from '../../../flagsmith-engine/index.js';
29
import { CONSTANTS } from '../../../flagsmith-engine/features/constants.js';
310
import { FeatureModel, FeatureStateModel } from '../../../flagsmith-engine/features/models.js';
411
import { TraitModel } from '../../../flagsmith-engine/identities/traits/models.js';
@@ -13,6 +20,9 @@ import {
1320
} from './utils.js';
1421
import { getEvaluationContext } from '../../../flagsmith-engine/evaluationContext/mappers.js';
1522
import { TARGETING_REASONS } from '../../../flagsmith-engine/features/types.js';
23+
import { flagsmith } from '../../sdk/utils.js';
24+
import { getIdentitySegments } from '../../../flagsmith-engine/segments/evaluators.js';
25+
import { EvaluationContext } from '../../../flagsmith-engine/evaluationContext/evaluationContext.types.js';
1626

1727
test('test_get_evaluation_result_without_any_override', () => {
1828
const context = getEvaluationContext(environment(), identity());
@@ -146,3 +156,270 @@ test('test_environment_get_all_feature_states', () => {
146156
// getEnvironmentFeatureStateFromContext(context, 'not_a_feature_name');
147157
// }).toThrowError('Feature State Not Found');
148158
// });
159+
160+
test('isHigherPriority should handle undefined priorities correctly', () => {
161+
expect(isHigherPriority(1, 2)).toBe(true);
162+
expect(isHigherPriority(2, 1)).toBe(false);
163+
expect(isHigherPriority(undefined, 5)).toBe(false);
164+
expect(isHigherPriority(5, undefined)).toBe(true);
165+
expect(isHigherPriority(undefined, undefined)).toBe(false);
166+
});
167+
168+
test('shouldApplyOverride with priority conflicts', () => {
169+
const existingOverrides: SegmentOverrides = {
170+
feature1: {
171+
feature: {
172+
key: 'key',
173+
feature_key: 'feature1',
174+
name: 'name',
175+
enabled: true,
176+
value: 'value',
177+
priority: 5
178+
},
179+
segmentName: 'segment1'
180+
}
181+
};
182+
183+
expect(shouldApplyOverride({ feature_key: 'feature1', priority: 2 }, existingOverrides)).toBe(
184+
true
185+
);
186+
expect(shouldApplyOverride({ feature_key: 'feature1', priority: 10 }, existingOverrides)).toBe(
187+
false
188+
);
189+
});
190+
191+
test('evaluateSegments handles segments with identity identifier matching', () => {
192+
const context: EvaluationContext = {
193+
environment: {
194+
key: 'test-env',
195+
name: 'Test Environment'
196+
},
197+
identity: {
198+
key: 'test-user',
199+
identifier: 'test-user'
200+
},
201+
segments: {
202+
'1': {
203+
key: '1',
204+
name: 'segment_with_no_overrides',
205+
rules: [
206+
{
207+
type: 'ALL',
208+
conditions: [
209+
{
210+
property: '$.identity.identifier',
211+
operator: 'EQUAL',
212+
value: 'test-user'
213+
}
214+
]
215+
}
216+
],
217+
overrides: []
218+
},
219+
'2': {
220+
key: '2',
221+
name: 'segment_with_overrides',
222+
rules: [
223+
{
224+
type: 'ALL',
225+
conditions: [
226+
{
227+
property: '$.identity.identifier',
228+
operator: 'EQUAL',
229+
value: 'test-user'
230+
}
231+
]
232+
}
233+
],
234+
overrides: [
235+
{
236+
key: 'override1',
237+
feature_key: 'feature1',
238+
name: 'feature1',
239+
enabled: true,
240+
value: 'overridden_value',
241+
priority: 1
242+
}
243+
]
244+
}
245+
},
246+
features: {
247+
feature1: {
248+
key: 'fs1',
249+
feature_key: 'feature1',
250+
name: 'feature1',
251+
enabled: false,
252+
value: 'default_value'
253+
}
254+
}
255+
};
256+
257+
const result = evaluateSegments(context);
258+
259+
expect(result.segments).toHaveLength(2);
260+
expect(result.segments).toEqual(
261+
expect.arrayContaining([
262+
{ key: '1', name: 'segment_with_no_overrides' },
263+
{ key: '2', name: 'segment_with_overrides' }
264+
])
265+
);
266+
267+
expect(Object.keys(result.segmentOverrides)).toEqual(['feature1']);
268+
expect(result.segmentOverrides.feature1.segmentName).toBe('segment_with_overrides');
269+
});
270+
271+
test('evaluateSegments handles priority conflicts correctly', () => {
272+
const context: EvaluationContext = {
273+
environment: {
274+
key: 'test-env',
275+
name: 'Test Environment'
276+
},
277+
identity: {
278+
key: 'test-user',
279+
identifier: 'test-user'
280+
},
281+
segments: {
282+
'1': {
283+
key: '1',
284+
name: 'low_priority_segment',
285+
rules: [
286+
{
287+
type: 'ALL',
288+
conditions: [
289+
{
290+
property: '$.identity.identifier',
291+
operator: 'EQUAL',
292+
value: 'test-user'
293+
}
294+
]
295+
}
296+
],
297+
overrides: [
298+
{
299+
key: 'override1',
300+
feature_key: 'feature1',
301+
name: 'feature1',
302+
enabled: true,
303+
value: 'low_priority_value',
304+
priority: 10
305+
}
306+
]
307+
},
308+
'2': {
309+
key: '2',
310+
name: 'high_priority_segment',
311+
rules: [
312+
{
313+
type: 'ALL',
314+
conditions: [
315+
{
316+
property: '$.identity.identifier',
317+
operator: 'EQUAL',
318+
value: 'test-user'
319+
}
320+
]
321+
}
322+
],
323+
overrides: [
324+
{
325+
key: 'override2',
326+
feature_key: 'feature1',
327+
name: 'feature1',
328+
enabled: false,
329+
value: 'high_priority_value',
330+
priority: 1
331+
}
332+
]
333+
}
334+
},
335+
features: {
336+
feature1: {
337+
key: 'fs1',
338+
feature_key: 'feature1',
339+
name: 'feature1',
340+
enabled: false,
341+
value: 'default_value'
342+
}
343+
}
344+
};
345+
346+
const result = evaluateSegments(context);
347+
348+
expect(result.segments).toHaveLength(2);
349+
350+
expect(result.segmentOverrides.feature1.segmentName).toBe('high_priority_segment');
351+
expect(result.segmentOverrides.feature1.feature.value).toBe('high_priority_value');
352+
expect(result.segmentOverrides.feature1.feature.priority).toBe(1);
353+
});
354+
355+
test('evaluateSegments with non-matching identity returns empty', () => {
356+
const context: EvaluationContext = {
357+
environment: {
358+
key: 'test-env',
359+
name: 'Test Environment'
360+
},
361+
identity: {
362+
key: 'test-user',
363+
identifier: 'test-user'
364+
},
365+
segments: {
366+
'1': {
367+
key: '1',
368+
name: 'segment_for_specific_user',
369+
rules: [
370+
{
371+
type: 'ALL',
372+
conditions: [
373+
{
374+
property: '$.identity.identifier',
375+
operator: 'EQUAL',
376+
value: 'test-user-123'
377+
}
378+
]
379+
}
380+
],
381+
overrides: [
382+
{
383+
key: 'override1',
384+
feature_key: 'feature1',
385+
name: 'feature1',
386+
enabled: true,
387+
value: 'overridden_value'
388+
}
389+
]
390+
}
391+
},
392+
features: {}
393+
};
394+
395+
const result = evaluateSegments(context);
396+
397+
expect(result.segments).toEqual([]);
398+
expect(result.segmentOverrides).toEqual({});
399+
});
400+
401+
test('evaluateFeatures with multivariate evaluation', () => {
402+
const context = {
403+
features: {
404+
mv_feature: {
405+
key: 'mv',
406+
feature_key: 'mv_feature',
407+
name: 'Multivariate Feature',
408+
enabled: true,
409+
value: 'default',
410+
variants: [
411+
{ value: 'variant_a', weight: 0 },
412+
{ value: 'variant_b', weight: 100 }
413+
]
414+
}
415+
},
416+
identity: { key: 'test_user', identifier: 'test_user' },
417+
environment: {
418+
key: 'test_env',
419+
name: 'Test Environment'
420+
}
421+
};
422+
423+
const result = evaluateFeatures(context, {});
424+
expect(result[0].value).toBe('variant_b');
425+
});

0 commit comments

Comments
 (0)