1 package evaluation
2
3 import (
4 "fmt"
5 "testing"
6
7 "github.com/launchdarkly/go-sdk-common/v3/ldattr"
8 "github.com/launchdarkly/go-sdk-common/v3/ldcontext"
9 "github.com/launchdarkly/go-sdk-common/v3/ldreason"
10 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
11 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
12 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
13
14 "github.com/stretchr/testify/assert"
15 )
16
17 const basicUserKey = "userkey"
18
19 type simpleBigSegmentProvider struct {
20 getMembership func(contextKey string) BigSegmentMembership
21 getStatus func(contextKey string) ldreason.BigSegmentsStatus
22 membershipKeysQueried []string
23 }
24
25 func basicBigSegmentsProvider() *simpleBigSegmentProvider {
26 return &simpleBigSegmentProvider{}
27 }
28
29 func (s *simpleBigSegmentProvider) GetMembership(contextKey string) (BigSegmentMembership, ldreason.BigSegmentsStatus) {
30 s.membershipKeysQueried = append(s.membershipKeysQueried, contextKey)
31 var membership BigSegmentMembership
32 if s.getMembership != nil {
33 membership = s.getMembership(contextKey)
34 }
35 status := ldreason.BigSegmentsHealthy
36 if s.getStatus != nil {
37 status = s.getStatus(contextKey)
38 }
39 return membership, status
40 }
41
42 func (s *simpleBigSegmentProvider) withStatus(status ldreason.BigSegmentsStatus) *simpleBigSegmentProvider {
43 return &simpleBigSegmentProvider{
44 getStatus: func(string) ldreason.BigSegmentsStatus { return status },
45 getMembership: s.getMembership,
46 }
47 }
48
49 func (s *simpleBigSegmentProvider) withStatusForKey(key string, status ldreason.BigSegmentsStatus) *simpleBigSegmentProvider {
50 previousGetStatus := s.getStatus
51 return &simpleBigSegmentProvider{
52 getStatus: func(queriedKey string) ldreason.BigSegmentsStatus {
53 if key == queriedKey {
54 return status
55 }
56 if previousGetStatus != nil {
57 return previousGetStatus(queriedKey)
58 }
59 return ldreason.BigSegmentsHealthy
60 },
61 getMembership: s.getMembership,
62 }
63 }
64
65 func (s *simpleBigSegmentProvider) withMembership(
66 key string,
67 membership *simpleMembership,
68 ) *simpleBigSegmentProvider {
69 previousGetMembership := s.getMembership
70 return &simpleBigSegmentProvider{
71 getStatus: s.getStatus,
72 getMembership: func(queriedKey string) BigSegmentMembership {
73 if key == queriedKey {
74 return membership
75 }
76 if previousGetMembership != nil {
77 return previousGetMembership(queriedKey)
78 }
79 return nil
80 },
81 }
82 }
83
84 type simpleMembership struct {
85 segmentChecks []string
86 included []string
87 excluded []string
88 }
89
90 func (m *simpleMembership) CheckMembership(segmentRef string) ldvalue.OptionalBool {
91 m.segmentChecks = append(m.segmentChecks, segmentRef)
92 for _, inc := range m.included {
93 if inc == segmentRef {
94 return ldvalue.NewOptionalBool(true)
95 }
96 }
97 for _, exc := range m.excluded {
98 if exc == segmentRef {
99 return ldvalue.NewOptionalBool(false)
100 }
101 }
102 return ldvalue.OptionalBool{}
103 }
104
105 func basicMembership() *simpleMembership { return &simpleMembership{} }
106
107 func (s *simpleMembership) include(segmentRefs ...string) *simpleMembership {
108 s.included = append(s.included, segmentRefs...)
109 return s
110 }
111
112 func (s *simpleMembership) exclude(segmentRefs ...string) *simpleMembership {
113 s.excluded = append(s.excluded, segmentRefs...)
114 return s
115 }
116
117 func TestBigSegmentWithNoProviderIsNotMatched(t *testing.T) {
118 evaluator := NewEvaluator(
119 basicDataProvider().withStoredSegments(
120 ldbuilders.NewSegmentBuilder("segmentkey").
121 Unbounded(true).
122 Generation(1).
123 Included(basicUserKey).
124 Build(),
125 ),
126 )
127 f := makeBooleanFlagToMatchAnyOfSegments("segmentkey")
128
129 result := evaluator.Evaluate(&f, ldcontext.New(basicUserKey), nil)
130 assert.Equal(t, ldvalue.Bool(false), result.Detail.Value)
131 assert.Equal(t, ldreason.BigSegmentsNotConfigured, result.Detail.Reason.GetBigSegmentsStatus())
132 }
133
134 func TestBigSegmentWithNoGenerationIsNotMatched(t *testing.T) {
135 segment := ldbuilders.NewSegmentBuilder("segmentkey").
136 Unbounded(true).
137 Build()
138 evaluator := NewEvaluatorWithOptions(
139 basicDataProvider().withStoredSegments(segment),
140 EvaluatorOptionBigSegmentProvider(basicBigSegmentsProvider().withMembership(basicUserKey,
141 basicMembership().include(makeBigSegmentRef(&segment)))),
142 )
143 f := makeBooleanFlagToMatchAnyOfSegments(segment.Key)
144
145 result := evaluator.Evaluate(&f, ldcontext.New(basicUserKey), nil)
146 assert.Equal(t, ldvalue.Bool(false), result.Detail.Value)
147 assert.Equal(t, ldreason.BigSegmentsNotConfigured, result.Detail.Reason.GetBigSegmentsStatus())
148 }
149
150 func TestBigSegmentMatch(t *testing.T) {
151 contextKey := "contextkey"
152 segmentKey := "segmentkey"
153 flag := makeBooleanFlagToMatchAnyOfSegments(segmentKey)
154 makeEvaluator := func(segment ldmodel.Segment, contextMembership *simpleMembership) Evaluator {
155 return NewEvaluatorWithOptions(
156 basicDataProvider().withStoredSegments(segment),
157 EvaluatorOptionBigSegmentProvider(basicBigSegmentsProvider().withMembership(contextKey, contextMembership)),
158 )
159 }
160 for _, contextKind := range []ldcontext.Kind{"", ldcontext.DefaultKind, "other"} {
161 for _, isMultiKind := range []bool{false, true} {
162 t.Run(fmt.Sprintf("kind=%s, isMultiKind=%t", contextKind, isMultiKind), func(t *testing.T) {
163 context := ldcontext.NewWithKind(contextKind, contextKey)
164 if isMultiKind {
165 context = ldcontext.NewMulti(context, ldcontext.NewWithKind("irrelevantKind", "irrelevantKey"))
166 }
167 contextWithoutDesiredKind := ldcontext.NewWithKind("irrelevantKind", contextKey)
168 if isMultiKind {
169 contextWithoutDesiredKind = ldcontext.NewMulti(contextWithoutDesiredKind,
170 ldcontext.NewWithKind("irrelevantKind2", "irrelevantKey"))
171 }
172
173 t.Run("includes", func(t *testing.T) {
174 segment := ldbuilders.NewSegmentBuilder(segmentKey).
175 Unbounded(true).
176 UnboundedContextKind(contextKind).
177 Generation(2).
178 Build()
179 evaluator := makeEvaluator(segment, basicMembership().include(makeBigSegmentRef(&segment)))
180
181 t.Run("matched by include", func(t *testing.T) {
182 result := evaluator.Evaluate(&flag, context, nil)
183 assert.Equal(t, ldvalue.Bool(true), result.Detail.Value)
184 assert.Equal(t, ldreason.BigSegmentsHealthy, result.Detail.Reason.GetBigSegmentsStatus())
185 })
186
187 t.Run("unmatched if context does not have specified kind", func(t *testing.T) {
188 result := evaluator.Evaluate(&flag, contextWithoutDesiredKind, nil)
189 assert.Equal(t, ldvalue.Bool(false), result.Detail.Value)
190
191
192 assert.Equal(t, ldreason.BigSegmentsStatus(""), result.Detail.Reason.GetBigSegmentsStatus())
193 })
194 })
195
196 t.Run("rule match", func(t *testing.T) {
197 segment := ldbuilders.NewSegmentBuilder(segmentKey).
198 Unbounded(true).
199 UnboundedContextKind(contextKind).
200 Generation(2).
201 AddRule(
202 ldbuilders.NewSegmentRuleBuilder().Clauses(makeClauseToMatchAnyContextOfKind(contextKind)),
203 ).
204 Build()
205 evaluator := makeEvaluator(segment, basicMembership())
206
207 t.Run("matched by rule", func(t *testing.T) {
208 result := evaluator.Evaluate(&flag, context, nil)
209 assert.Equal(t, ldvalue.Bool(true), result.Detail.Value)
210 assert.Equal(t, ldreason.BigSegmentsHealthy, result.Detail.Reason.GetBigSegmentsStatus())
211 })
212
213 t.Run("rules ignored if context does not have specified kind", func(t *testing.T) {
214 result := evaluator.Evaluate(&flag, contextWithoutDesiredKind, nil)
215 assert.Equal(t, ldvalue.Bool(false), result.Detail.Value)
216
217
218 assert.Equal(t, ldreason.BigSegmentsStatus(""), result.Detail.Reason.GetBigSegmentsStatus())
219 })
220
221 t.Run("exclude takes priority over rule", func(t *testing.T) {
222 evaluatorWithExclude := makeEvaluator(segment,
223 basicMembership().exclude(makeBigSegmentRef(&segment)))
224 result := evaluatorWithExclude.Evaluate(&flag, context, nil)
225 assert.Equal(t, ldvalue.Bool(false), result.Detail.Value)
226 assert.Equal(t, ldreason.BigSegmentsHealthy, result.Detail.Reason.GetBigSegmentsStatus())
227 })
228 })
229 })
230 }
231 }
232 }
233
234 func TestBigSegmentIsMatchedWithRuleWhenSegmentDataForUserShowsNoMatch(t *testing.T) {
235 segment := ldbuilders.NewSegmentBuilder("segmentkey").
236 Unbounded(true).
237 Generation(2).
238 AddRule(ldbuilders.NewSegmentRuleBuilder().
239 Clauses(ldbuilders.Clause(ldattr.KeyAttr, ldmodel.OperatorIn, ldvalue.String(basicUserKey)))).
240 Build()
241 evaluator := NewEvaluatorWithOptions(
242 basicDataProvider().withStoredSegments(segment),
243 EvaluatorOptionBigSegmentProvider(
244 basicBigSegmentsProvider().withMembership(basicUserKey, basicMembership())),
245 )
246 f := makeBooleanFlagToMatchAnyOfSegments(segment.Key)
247
248 result := evaluator.Evaluate(&f, ldcontext.New(basicUserKey), nil)
249 assert.Equal(t, ldvalue.Bool(true), result.Detail.Value)
250 assert.Equal(t, ldreason.BigSegmentsHealthy, result.Detail.Reason.GetBigSegmentsStatus())
251 }
252
253 func TestBigSegmentStatusIsReturnedFromProvider(t *testing.T) {
254 segment := ldbuilders.NewSegmentBuilder("segmentkey").
255 Unbounded(true).
256 Generation(2).
257 Build()
258 evaluator := NewEvaluatorWithOptions(
259 basicDataProvider().withStoredSegments(segment),
260 EvaluatorOptionBigSegmentProvider(basicBigSegmentsProvider().
261 withMembership(basicUserKey, basicMembership().include(makeBigSegmentRef(&segment))).
262 withStatus(ldreason.BigSegmentsStale)),
263 )
264 f := makeBooleanFlagToMatchAnyOfSegments(segment.Key)
265
266 result := evaluator.Evaluate(&f, ldcontext.New(basicUserKey), nil)
267 assert.Equal(t, ldvalue.Bool(true), result.Detail.Value)
268 assert.Equal(t, ldreason.BigSegmentsStale, result.Detail.Reason.GetBigSegmentsStatus())
269 }
270
271 func TestBigSegmentStateIsQueriedOnlyOncePerUniqueContextKey(t *testing.T) {
272 segmentKey1, segmentKey2 := "segmentKey1", "segmentKey2"
273 flag := makeBooleanFlagToMatchAllOfSegments(segmentKey1, segmentKey2)
274
275
276
277 t.Run("single context kind", func(t *testing.T) {
278 contextKey := "contextKey"
279 context := ldcontext.New(contextKey)
280 segment1 := ldbuilders.NewSegmentBuilder(segmentKey1).Unbounded(true).Generation(1).Build()
281 segment2 := ldbuilders.NewSegmentBuilder(segmentKey2).Unbounded(true).Generation(2).Build()
282 membership := basicMembership().include(makeBigSegmentRef(&segment1), makeBigSegmentRef(&segment2))
283 bigSegmentsProvider := basicBigSegmentsProvider().withMembership(contextKey, membership)
284 evaluator := NewEvaluatorWithOptions(
285 basicDataProvider().withStoredSegments(segment1, segment2),
286 EvaluatorOptionBigSegmentProvider(bigSegmentsProvider),
287 )
288
289 result := evaluator.Evaluate(&flag, context, nil)
290
291 assert.Equal(t, ldvalue.Bool(true), result.Detail.Value)
292 assert.Equal(t, ldreason.BigSegmentsHealthy, result.Detail.Reason.GetBigSegmentsStatus())
293 assert.Equal(t, []string{contextKey}, bigSegmentsProvider.membershipKeysQueried)
294 assert.Equal(t, []string{makeBigSegmentRef(&segment1), makeBigSegmentRef(&segment2)}, membership.segmentChecks)
295 })
296
297 t.Run("two context kinds referenced, both have same key", func(t *testing.T) {
298 contextKey := "contextKey"
299 otherKind := ldcontext.Kind("other")
300 context := ldcontext.NewMulti(
301 ldcontext.New(contextKey),
302 ldcontext.NewWithKind(otherKind, contextKey),
303 )
304 segment1 := ldbuilders.NewSegmentBuilder(segmentKey1).Unbounded(true).Generation(1).Build()
305 segment2 := ldbuilders.NewSegmentBuilder(segmentKey2).Unbounded(true).Generation(2).
306 UnboundedContextKind(otherKind).Build()
307 membership := basicMembership().include(makeBigSegmentRef(&segment1), makeBigSegmentRef(&segment2))
308 bigSegmentsProvider := basicBigSegmentsProvider().withMembership(contextKey, membership)
309 evaluator := NewEvaluatorWithOptions(
310 basicDataProvider().withStoredSegments(segment1, segment2),
311 EvaluatorOptionBigSegmentProvider(bigSegmentsProvider),
312 )
313
314 result := evaluator.Evaluate(&flag, context, nil)
315
316 assert.Equal(t, ldvalue.Bool(true), result.Detail.Value)
317 assert.Equal(t, ldreason.BigSegmentsHealthy, result.Detail.Reason.GetBigSegmentsStatus())
318 assert.Equal(t, []string{contextKey}, bigSegmentsProvider.membershipKeysQueried)
319 assert.Equal(t, []string{makeBigSegmentRef(&segment1), makeBigSegmentRef(&segment2)}, membership.segmentChecks)
320 })
321
322 t.Run("two context kinds referenced, each with a different key", func(t *testing.T) {
323 contextKey1, contextKey2 := "contextKey1", "contextKey2"
324 otherKind := ldcontext.Kind("other")
325 context := ldcontext.NewMulti(
326 ldcontext.New(contextKey1),
327 ldcontext.NewWithKind(otherKind, contextKey2),
328 )
329 segment1 := ldbuilders.NewSegmentBuilder(segmentKey1).Unbounded(true).Generation(1).Build()
330 segment2 := ldbuilders.NewSegmentBuilder(segmentKey2).Unbounded(true).Generation(2).
331 UnboundedContextKind(otherKind).Build()
332 membershipForKey1 := basicMembership().include(makeBigSegmentRef(&segment1))
333 membershipForKey2 := basicMembership().include(makeBigSegmentRef(&segment2))
334 bigSegmentsProvider := basicBigSegmentsProvider().
335 withMembership(contextKey1, membershipForKey1).
336 withMembership(contextKey2, membershipForKey2)
337 evaluator := NewEvaluatorWithOptions(
338 basicDataProvider().withStoredSegments(segment1, segment2),
339 EvaluatorOptionBigSegmentProvider(bigSegmentsProvider),
340 )
341
342 result := evaluator.Evaluate(&flag, context, nil)
343
344 assert.Equal(t, ldvalue.Bool(true), result.Detail.Value)
345 assert.Equal(t, ldreason.BigSegmentsHealthy, result.Detail.Reason.GetBigSegmentsStatus())
346 assert.Equal(t, []string{contextKey1, contextKey2}, bigSegmentsProvider.membershipKeysQueried)
347 assert.Equal(t, []string{makeBigSegmentRef(&segment1)}, membershipForKey1.segmentChecks)
348 assert.Equal(t, []string{makeBigSegmentRef(&segment2)}, membershipForKey2.segmentChecks)
349 })
350 }
351
352 func TestBigSegmentStatusWithMultipleQueries(t *testing.T) {
353
354
355
356 segmentKey1, segmentKey2 := "segmentKey1", "segmentKey2"
357 contextKey1, contextKey2 := "contextKey1", "contextKey2"
358 otherKind := ldcontext.Kind("other")
359 context := ldcontext.NewMulti(
360 ldcontext.New(contextKey1),
361 ldcontext.NewWithKind(otherKind, contextKey2),
362 )
363 segment1 := ldbuilders.NewSegmentBuilder(segmentKey1).Unbounded(true).Generation(1).Build()
364 segment2 := ldbuilders.NewSegmentBuilder(segmentKey2).Unbounded(true).Generation(2).
365 UnboundedContextKind(otherKind).Build()
366 membershipForKey1 := basicMembership().include(makeBigSegmentRef(&segment1))
367 membershipForKey2 := basicMembership().include(makeBigSegmentRef(&segment2))
368 flag := makeBooleanFlagToMatchAllOfSegments(segmentKey1, segmentKey2)
369
370 type params struct{ status1, status2, expected ldreason.BigSegmentsStatus }
371 var allParams []params
372 allStatuses := []ldreason.BigSegmentsStatus{ldreason.BigSegmentsHealthy, ldreason.BigSegmentsStale,
373 ldreason.BigSegmentsStoreError, ldreason.BigSegmentsNotConfigured}
374 for i := 0; i < len(allStatuses)-1; i++ {
375 better, worse := allStatuses[i], allStatuses[i+1]
376 allParams = append(allParams, params{better, worse, worse})
377 allParams = append(allParams, params{worse, better, worse})
378 }
379 for _, p := range allParams {
380 t.Run(fmt.Sprintf("%s, %s", p.status1, p.status2), func(t *testing.T) {
381 bigSegmentsProvider := basicBigSegmentsProvider().
382 withMembership(contextKey1, membershipForKey1).
383 withMembership(contextKey2, membershipForKey2).
384 withStatusForKey(contextKey1, p.status1).
385 withStatusForKey(contextKey2, p.status2)
386 evaluator := NewEvaluatorWithOptions(
387 basicDataProvider().withStoredSegments(segment1, segment2),
388 EvaluatorOptionBigSegmentProvider(bigSegmentsProvider),
389 )
390
391 result := evaluator.Evaluate(&flag, context, nil)
392
393 assert.Equal(t, ldvalue.Bool(true), result.Detail.Value)
394 assert.Equal(t, []string{contextKey1, contextKey2}, bigSegmentsProvider.membershipKeysQueried)
395 assert.Equal(t, p.expected, result.Detail.Reason.GetBigSegmentsStatus())
396 })
397 }
398 }
399
400 func TestBigSegmentStatusIsReturnedWhenBigSegmentWasReferencedFromPrerequisiteFlag(t *testing.T) {
401 segment := ldbuilders.NewSegmentBuilder("segmentkey").
402 Unbounded(true).
403 Generation(2).
404 Build()
405
406 f1 := makeBooleanFlagToMatchAnyOfSegments(segment.Key)
407 f0 := ldbuilders.NewFlagBuilder("feature0").
408 On(true).
409 Variations(ldvalue.Bool(false), ldvalue.Bool(true)).FallthroughVariation(1).
410 AddPrerequisite(f1.Key, 1).
411 Build()
412
413 evaluator := NewEvaluatorWithOptions(
414 basicDataProvider().withStoredFlags(f1).withStoredSegments(segment),
415 EvaluatorOptionBigSegmentProvider(basicBigSegmentsProvider().
416 withMembership(basicUserKey, basicMembership().include(makeBigSegmentRef(&segment))).
417 withStatus(ldreason.BigSegmentsStale)),
418 )
419
420 result := evaluator.Evaluate(&f0, ldcontext.New(basicUserKey), nil)
421 assert.Equal(t, ldvalue.Bool(true), result.Detail.Value)
422 assert.Equal(t, ldreason.BigSegmentsStale, result.Detail.Reason.GetBigSegmentsStatus())
423 }
424
View as plain text