1 package ldmodel
2
3 import (
4 "regexp"
5 "testing"
6 "time"
7
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10
11 "github.com/launchdarkly/go-sdk-common/v3/ldcontext"
12 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
13 )
14
15 func TestPreprocessFlagBuildsTargetMap(t *testing.T) {
16 f := FeatureFlag{
17 Targets: []Target{
18 {
19 Variation: 0,
20 Values: nil,
21 },
22 {
23 Variation: 1,
24 Values: []string{"a", "b"},
25 },
26 },
27 }
28
29 assert.Nil(t, f.Targets[0].preprocessed.valuesMap)
30 assert.Nil(t, f.Targets[1].preprocessed.valuesMap)
31
32 PreprocessFlag(&f)
33
34 assert.Nil(t, f.Targets[0].preprocessed.valuesMap)
35
36 assert.Len(t, f.Targets[1].preprocessed.valuesMap, 2)
37 assert.Contains(t, f.Targets[1].preprocessed.valuesMap, "a")
38 assert.Contains(t, f.Targets[1].preprocessed.valuesMap, "b")
39 }
40
41 func TestPreprocessFlagCreatesClauseValuesMapForMultiValueEqualityTest(t *testing.T) {
42 f := FeatureFlag{
43 Rules: []FlagRule{
44 {
45 Clauses: []Clause{
46 {
47 Op: OperatorIn,
48 Values: []ldvalue.Value{ldvalue.Bool(true), ldvalue.String("a"), ldvalue.Int(0)},
49 },
50 },
51 },
52 },
53 }
54
55 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.valuesMap)
56
57 PreprocessFlag(&f)
58
59 m := f.Rules[0].Clauses[0].preprocessed.valuesMap
60 assert.Equal(t, map[jsonPrimitiveValueKey]struct{}{
61 asPrimitiveValueKey(ldvalue.Bool(true)): {},
62 asPrimitiveValueKey(ldvalue.String("a")): {},
63 asPrimitiveValueKey(ldvalue.Int(0)): {},
64 }, m)
65 }
66
67 func TestPreprocessFlagDoesNotCreateClauseValuesMapForSingleValueEqualityTest(t *testing.T) {
68 f := FeatureFlag{
69 Rules: []FlagRule{
70 {
71 Clauses: []Clause{
72 {
73 Op: OperatorIn,
74 Values: []ldvalue.Value{ldvalue.String("a")},
75 },
76 },
77 },
78 },
79 }
80
81 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.valuesMap)
82
83 PreprocessFlag(&f)
84
85 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.valuesMap)
86 }
87
88 func TestPreprocessFlagDoesNotCreateClauseValuesMapForEmptyEqualityTest(t *testing.T) {
89 f := FeatureFlag{
90 Rules: []FlagRule{
91 {Clauses: []Clause{{Op: OperatorIn, Values: []ldvalue.Value{}}}},
92 },
93 }
94
95 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.valuesMap)
96
97 PreprocessFlag(&f)
98
99 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.valuesMap)
100 }
101
102 func TestPreprocessFlagDoesNotCreateClauseValuesMapForNonEqualityOperators(t *testing.T) {
103 ops := []Operator{
104 OperatorEndsWith, OperatorStartsWith, OperatorMatches, OperatorContains, OperatorLessThan,
105 OperatorLessThanOrEqual, OperatorGreaterThan, OperatorGreaterThanOrEqual, OperatorBefore,
106 OperatorAfter, OperatorSegmentMatch, OperatorSemVerEqual, OperatorSemVerLessThan,
107 OperatorSemVerGreaterThan,
108 }
109
110 values := []ldvalue.Value{ldvalue.String("a"), ldvalue.String("b")}
111
112
113
114 for _, op := range ops {
115 t.Run(string(op), func(t *testing.T) {
116 f := FeatureFlag{
117 Rules: []FlagRule{
118 {
119 Clauses: []Clause{{Op: op, Values: values}},
120 },
121 },
122 }
123
124 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.valuesMap)
125
126 PreprocessFlag(&f)
127
128 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.valuesMap)
129 })
130 }
131 }
132
133 func TestPreprocessFlagParsesClauseRegex(t *testing.T) {
134 f := FeatureFlag{
135 Rules: []FlagRule{
136 {
137 Clauses: []Clause{
138 {
139 Op: OperatorMatches,
140 Values: []ldvalue.Value{ldvalue.String("x*"), ldvalue.String("\\"), ldvalue.Int(3)},
141 },
142 },
143 },
144 },
145 }
146
147 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.values)
148
149 PreprocessFlag(&f)
150
151 p := f.Rules[0].Clauses[0].preprocessed.values
152 require.Len(t, p, 3)
153
154 assert.True(t, p[0].computed)
155 assert.True(t, p[0].valid)
156 assert.Equal(t, regexp.MustCompile("x*"), p[0].parsedRegexp)
157
158 assert.True(t, p[1].computed)
159 assert.False(t, p[1].valid)
160 assert.True(t, p[2].computed)
161 assert.False(t, p[2].valid)
162 }
163
164 func TestPreprocessFlagParsesClauseTime(t *testing.T) {
165 time1Str := "2016-04-16T17:09:12-07:00"
166 t1, _ := time.Parse(time.RFC3339Nano, time1Str)
167 time1 := t1.UTC()
168 time2Num := float64(1000000)
169 time2 := time.Unix(0, int64(time2Num)*int64(time.Millisecond)).UTC()
170
171 for _, operator := range []Operator{OperatorAfter, OperatorBefore} {
172 t.Run(string(operator), func(t *testing.T) {
173 f := FeatureFlag{
174 Rules: []FlagRule{
175 {
176 Clauses: []Clause{
177 {
178 Op: operator,
179 Values: []ldvalue.Value{ldvalue.String(time1Str), ldvalue.Float64(time2Num), ldvalue.String("x"), ldvalue.Bool(false)},
180 },
181 },
182 },
183 },
184 }
185
186 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.values)
187
188 PreprocessFlag(&f)
189
190 p := f.Rules[0].Clauses[0].preprocessed.values
191 require.Len(t, p, 4)
192
193 assert.True(t, p[0].computed)
194 assert.True(t, p[0].valid)
195 assert.Equal(t, time1, p[0].parsedTime)
196
197 assert.True(t, p[1].computed)
198 assert.True(t, p[1].valid)
199 assert.Equal(t, time2, p[1].parsedTime)
200
201 assert.True(t, p[2].computed)
202 assert.False(t, p[2].valid)
203 assert.True(t, p[3].computed)
204 assert.False(t, p[3].valid)
205 })
206 }
207 }
208
209 func TestPreprocessFlagParsesClauseSemver(t *testing.T) {
210 expected, ok := parseSemVer(ldvalue.String("1.2.3"))
211 require.True(t, ok)
212
213 for _, operator := range []Operator{OperatorSemVerEqual, OperatorSemVerGreaterThan, OperatorSemVerLessThan} {
214 t.Run(string(operator), func(t *testing.T) {
215 f := FeatureFlag{
216 Rules: []FlagRule{
217 {
218 Clauses: []Clause{
219 {
220 Op: operator,
221 Values: []ldvalue.Value{ldvalue.String("1.2.3"), ldvalue.String("x"), ldvalue.Bool(false)},
222 },
223 },
224 },
225 },
226 }
227
228 assert.Nil(t, f.Rules[0].Clauses[0].preprocessed.values)
229
230 PreprocessFlag(&f)
231
232 p := f.Rules[0].Clauses[0].preprocessed.values
233 require.Len(t, p, 3)
234
235 assert.True(t, p[0].computed)
236 assert.True(t, p[0].valid)
237 assert.Equal(t, expected, p[0].parsedSemver)
238
239 assert.True(t, p[1].computed)
240 assert.False(t, p[1].valid)
241 assert.True(t, p[2].computed)
242 assert.False(t, p[2].valid)
243 })
244 }
245 }
246
247 func TestPreprocessSegmentBuildsIncludeAndExcludeMaps(t *testing.T) {
248 s := Segment{
249 Included: []string{"a", "b"},
250 Excluded: []string{"c"},
251 IncludedContexts: []SegmentTarget{
252 {ContextKind: ldcontext.Kind("org"), Values: []string{"x", "y"}},
253 },
254 ExcludedContexts: []SegmentTarget{
255 {ContextKind: ldcontext.Kind("org"), Values: []string{"z"}},
256 },
257 }
258
259 assert.Nil(t, s.preprocessed.includeMap)
260 assert.Nil(t, s.preprocessed.excludeMap)
261
262 PreprocessSegment(&s)
263
264 assert.Equal(t, map[string]struct{}{"a": {}, "b": {}}, s.preprocessed.includeMap)
265 assert.Equal(t, map[string]struct{}{"c": {}}, s.preprocessed.excludeMap)
266 assert.Equal(t, map[string]struct{}{"x": {}, "y": {}}, s.IncludedContexts[0].preprocessed.valuesMap)
267 assert.Equal(t, map[string]struct{}{"z": {}}, s.ExcludedContexts[0].preprocessed.valuesMap)
268 }
269
270 func TestPreprocessSegmentPreprocessesClausesInRules(t *testing.T) {
271
272 s := Segment{
273 Rules: []SegmentRule{
274 {
275 Clauses: []Clause{
276 {
277 Op: OperatorMatches,
278 Values: []ldvalue.Value{ldvalue.String("x*"), ldvalue.String("\\"), ldvalue.Int(3)},
279 },
280 },
281 },
282 },
283 }
284
285 assert.Nil(t, s.Rules[0].Clauses[0].preprocessed.values)
286
287 PreprocessSegment(&s)
288
289 p := s.Rules[0].Clauses[0].preprocessed.values
290 require.Len(t, p, 3)
291
292 assert.True(t, p[0].computed)
293 assert.True(t, p[0].valid)
294 assert.Equal(t, regexp.MustCompile("x*"), p[0].parsedRegexp)
295
296 assert.True(t, p[1].computed)
297 assert.False(t, p[1].valid)
298 assert.True(t, p[2].computed)
299 assert.False(t, p[2].valid)
300 }
301
View as plain text