Skip to content

Commit 65ae7b1

Browse files
Unit tests
1 parent 00e9473 commit 65ae7b1

File tree

3 files changed

+114
-9
lines changed

3 files changed

+114
-9
lines changed

src/evaluator/Engine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function engineParser(log: ILogger, split: ISplit, storage: IStorageSync
6262
};
6363
}
6464

65-
const prerequisitesMet = prerequisiteMatcher(key, attributes, splitEvaluator);
65+
const prerequisitesMet = prerequisiteMatcher({ key, attributes }, splitEvaluator);
6666

6767
return thenable(prerequisitesMet) ?
6868
prerequisitesMet.then(evaluate) :
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { evaluateFeature } from '../../index';
2+
import { IStorageSync } from '../../../storages/types';
3+
import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock';
4+
import { ISplit } from '../../../dtos/types';
5+
import { ALWAYS_ON_SPLIT, ALWAYS_OFF_SPLIT } from '../../../storages/__tests__/testUtils';
6+
import { prerequisitesMatcherContext } from '../prerequisites';
7+
8+
const STORED_SPLITS: Record<string, ISplit> = {
9+
'always-on': ALWAYS_ON_SPLIT,
10+
'always-off': ALWAYS_OFF_SPLIT
11+
};
12+
13+
const mockStorage = {
14+
splits: {
15+
getSplit: (name: string) => STORED_SPLITS[name]
16+
}
17+
} as IStorageSync;
18+
19+
test('MATCHER PREREQUISITES / should return true when all prerequisites are met', () => {
20+
// A single prerequisite
21+
const matcherTrueAlwaysOn = prerequisitesMatcherContext([{
22+
n: 'always-on',
23+
ts: ['not-existing', 'on', 'other'] // We should match from a list of treatments
24+
}], mockStorage, loggerMock);
25+
expect(matcherTrueAlwaysOn({ key: 'a-key' }, evaluateFeature)).toBe(true); // Feature flag returns one of the expected treatments, so the matcher returns true
26+
27+
const matcherFalseAlwaysOn = prerequisitesMatcherContext([{
28+
n: 'always-on',
29+
ts: ['off', 'v1']
30+
}], mockStorage, loggerMock);
31+
expect(matcherFalseAlwaysOn({ key: 'a-key' }, evaluateFeature)).toBe(false); // Feature flag returns treatment "on", but we are expecting ["off", "v1"], so the matcher returns false
32+
33+
const matcherTrueAlwaysOff = prerequisitesMatcherContext([{
34+
n: 'always-off',
35+
ts: ['not-existing', 'off']
36+
}], mockStorage, loggerMock);
37+
expect(matcherTrueAlwaysOff({ key: 'a-key' }, evaluateFeature)).toBe(true); // Feature flag returns one of the expected treatments, so the matcher returns true
38+
39+
const matcherFalseAlwaysOff = prerequisitesMatcherContext([{
40+
n: 'always-off',
41+
ts: ['v1', 'on']
42+
}], mockStorage, loggerMock);
43+
expect(matcherFalseAlwaysOff({ key: 'a-key' }, evaluateFeature)).toBe(false); // Feature flag returns treatment "on", but we are expecting ["off", "v1"], so the matcher returns false
44+
45+
// Multiple prerequisites
46+
const matcherTrueMultiplePrerequisites = prerequisitesMatcherContext([
47+
{
48+
n: 'always-on',
49+
ts: ['on']
50+
},
51+
{
52+
n: 'always-off',
53+
ts: ['off']
54+
}
55+
], mockStorage, loggerMock);
56+
expect(matcherTrueMultiplePrerequisites({ key: 'a-key' }, evaluateFeature)).toBe(true); // All prerequisites are met, so the matcher returns true
57+
58+
const matcherFalseMultiplePrerequisites = prerequisitesMatcherContext([
59+
{
60+
n: 'always-on',
61+
ts: ['on']
62+
},
63+
{
64+
n: 'always-off',
65+
ts: ['on']
66+
}
67+
], mockStorage, loggerMock);
68+
expect(matcherFalseMultiplePrerequisites({ key: 'a-key' }, evaluateFeature)).toBe(false); // One of the prerequisites is not met, so the matcher returns false
69+
});
70+
71+
test('MATCHER PREREQUISITES / Edge cases', () => {
72+
// No prerequisites
73+
const matcherTrueNoPrerequisites = prerequisitesMatcherContext(undefined, mockStorage, loggerMock);
74+
expect(matcherTrueNoPrerequisites({ key: 'a-key' }, evaluateFeature)).toBe(true);
75+
76+
const matcherTrueEmptyPrerequisites = prerequisitesMatcherContext([], mockStorage, loggerMock);
77+
expect(matcherTrueEmptyPrerequisites({ key: 'a-key' }, evaluateFeature)).toBe(true);
78+
79+
// Non existent feature flag
80+
const matcherParentNotExist = prerequisitesMatcherContext([{
81+
n: 'not-existent-feature-flag',
82+
ts: ['on', 'off']
83+
}], mockStorage, loggerMock);
84+
expect(matcherParentNotExist({ key: 'a-key' }, evaluateFeature)).toBe(false); // If the feature flag does not exist, matcher should return false
85+
86+
// Empty treatments list
87+
const matcherNoTreatmentsExpected = prerequisitesMatcherContext([
88+
{
89+
n: 'always-on',
90+
ts: []
91+
}], mockStorage, loggerMock);
92+
expect(matcherNoTreatmentsExpected({ key: 'a-key' }, evaluateFeature)).toBe(false); // If treatments expectation list is empty, matcher should return false (no treatment will match)
93+
94+
const matcherExpectedTreatmentWrongTypeMatching = prerequisitesMatcherContext([{
95+
n: 'always-on', // @ts-ignore
96+
ts: [null, [1, 2], 3, {}, true, 'on']
97+
98+
}], mockStorage, loggerMock);
99+
expect(matcherExpectedTreatmentWrongTypeMatching({ key: 'a-key' }, evaluateFeature)).toBe(true); // If treatments expectation list has elements of the wrong type, those elements are overlooked.
100+
101+
const matcherExpectedTreatmentWrongTypeNotMatching = prerequisitesMatcherContext([{
102+
n: 'always-off', // @ts-ignore
103+
ts: [null, [1, 2], 3, {}, true, 'on']
104+
}], mockStorage, loggerMock);
105+
expect(matcherExpectedTreatmentWrongTypeNotMatching({ key: 'a-key' }, evaluateFeature)).toBe(false); // If treatments expectation list has elements of the wrong type, those elements are overlooked.
106+
});

src/evaluator/matchers/prerequisites.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,23 @@ import { ISplit, MaybeThenable } from '../../dtos/types';
22
import { IStorageAsync, IStorageSync } from '../../storages/types';
33
import { ILogger } from '../../logger/types';
44
import { thenable } from '../../utils/promise/thenable';
5-
import { ISplitEvaluator } from '../types';
6-
import SplitIO from '../../../types/splitio';
5+
import { IDependencyMatcherValue, ISplitEvaluator } from '../types';
76

87
export function prerequisitesMatcherContext(prerequisites: ISplit['prerequisites'] = [], storage: IStorageSync | IStorageAsync, log: ILogger) {
98

10-
return function prerequisitesMatcher(key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, splitEvaluator: ISplitEvaluator): MaybeThenable<boolean> {
9+
return function prerequisitesMatcher({ key, attributes }: IDependencyMatcherValue, splitEvaluator: ISplitEvaluator): MaybeThenable<boolean> {
1110

1211
function evaluatePrerequisite(prerequisite: { n: string; ts: string[] }): MaybeThenable<boolean> {
1312
const evaluation = splitEvaluator(log, key, prerequisite.n, attributes, storage);
14-
return thenable(evaluation)
15-
? evaluation.then(evaluation => prerequisite.ts.indexOf(evaluation.treatment!) === -1)
16-
: prerequisite.ts.indexOf(evaluation.treatment!) === -1;
13+
return thenable(evaluation) ?
14+
evaluation.then(evaluation => prerequisite.ts.indexOf(evaluation.treatment!) !== -1) :
15+
prerequisite.ts.indexOf(evaluation.treatment!) !== -1;
1716
}
1817

1918
return prerequisites.reduce<MaybeThenable<boolean>>((prerequisitesMet, prerequisite) => {
2019
return thenable(prerequisitesMet) ?
21-
prerequisitesMet.then(prerequisitesMet => !prerequisitesMet ? false : evaluatePrerequisite(prerequisite)) :
22-
!prerequisitesMet ? false : evaluatePrerequisite(prerequisite);
20+
prerequisitesMet.then(prerequisitesMet => prerequisitesMet ? evaluatePrerequisite(prerequisite) : false) :
21+
prerequisitesMet ? evaluatePrerequisite(prerequisite) : false;
2322
}, true);
2423
};
2524
}

0 commit comments

Comments
 (0)