1 package ldclient
2
3 import (
4 "encoding/json"
5 "errors"
6 "testing"
7
8 "github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks"
9
10 "github.com/launchdarkly/go-sdk-common/v3/ldcontext"
11 "github.com/launchdarkly/go-sdk-common/v3/ldlog"
12 "github.com/launchdarkly/go-sdk-common/v3/ldlogtest"
13 "github.com/launchdarkly/go-sdk-common/v3/ldreason"
14 "github.com/launchdarkly/go-sdk-common/v3/lduser"
15 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
16 ldevents "github.com/launchdarkly/go-sdk-events/v2"
17 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
18 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
19 "github.com/launchdarkly/go-server-sdk/v6/internal/datakinds"
20 "github.com/launchdarkly/go-server-sdk/v6/internal/datastore"
21 "github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest"
22 "github.com/launchdarkly/go-server-sdk/v6/ldcomponents"
23 "github.com/launchdarkly/go-server-sdk/v6/subsystems"
24 "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes"
25 "github.com/launchdarkly/go-server-sdk/v6/testhelpers/ldtestdata"
26
27 "github.com/stretchr/testify/assert"
28 "github.com/stretchr/testify/require"
29 )
30
31 const (
32 evalFlagKey = "flag-key"
33 expectedVariationForSingleValueFlag = 2
34 expectedFlagVersion = 1
35 )
36
37 var evalTestUser = lduser.NewUser("userkey")
38
39 var fallthroughValue = ldvalue.String("fall")
40 var offValue = ldvalue.String("off")
41 var onValue = ldvalue.String("on")
42
43 var expectedReasonForSingleValueFlag = ldreason.NewEvalReasonFallthrough()
44 var noReason = ldreason.EvaluationReason{}
45
46 func makeClauseToMatchUser(user ldcontext.Context) ldmodel.Clause {
47 return ldbuilders.Clause("key", ldmodel.OperatorIn, ldvalue.String(user.Key()))
48 }
49
50 func makeClauseToNotMatchUser(user ldcontext.Context) ldmodel.Clause {
51 return ldbuilders.Clause("key", ldmodel.OperatorIn, ldvalue.String("not-"+user.Key()))
52 }
53
54 type clientEvalTestParams struct {
55 client *LDClient
56 store subsystems.DataStore
57 data *ldtestdata.TestDataSource
58 events *mocks.CapturingEventProcessor
59 mockLog *ldlogtest.MockLog
60 }
61
62 func (p clientEvalTestParams) setupSingleValueFlag(key string, value ldvalue.Value) {
63 values := []ldvalue.Value{}
64 for i := 0; i < expectedVariationForSingleValueFlag; i++ {
65
66
67 values = append(values, ldvalue.String("should not get this value"))
68 }
69 values = append(values, value)
70 p.data.Update(p.data.Flag(key).On(true).
71 FallthroughVariationIndex(expectedVariationForSingleValueFlag).
72 Variations(values...))
73 }
74
75 func withClientEvalTestParams(callback func(clientEvalTestParams)) {
76 p := clientEvalTestParams{}
77 p.store = datastore.NewInMemoryDataStore(ldlog.NewDisabledLoggers())
78 p.data = ldtestdata.DataSource()
79 p.events = &mocks.CapturingEventProcessor{}
80 p.mockLog = ldlogtest.NewMockLog()
81 config := Config{
82 Offline: false,
83 DataStore: mocks.SingleComponentConfigurer[subsystems.DataStore]{Instance: p.store},
84 DataSource: p.data,
85 Events: mocks.SingleComponentConfigurer[ldevents.EventProcessor]{Instance: p.events},
86 Logging: ldcomponents.Logging().Loggers(p.mockLog.Loggers),
87 }
88 p.client, _ = MakeCustomClient("sdk_key", config, 0)
89 defer p.client.Close()
90 callback(p)
91 }
92
93 func (p clientEvalTestParams) requireSingleEvent(t *testing.T) ldevents.EvaluationData {
94 events := p.events.Events
95 require.Equal(t, 1, len(events))
96 return events[0].(ldevents.EvaluationData)
97 }
98
99 func (p clientEvalTestParams) expectSingleEvaluationEvent(
100 t *testing.T,
101 flagKey string,
102 value ldvalue.Value,
103 defaultVal ldvalue.Value,
104 reason ldreason.EvaluationReason,
105 ) {
106 assertEvalEvent(t, p.requireSingleEvent(t), flagKey, expectedFlagVersion, evalTestUser, value,
107 expectedVariationForSingleValueFlag, defaultVal, reason)
108 }
109
110 func assertEvalEvent(
111 t *testing.T,
112 actualEvent ldevents.EvaluationData,
113 flagKey string,
114 flagVersion int,
115 user ldcontext.Context,
116 value ldvalue.Value,
117 variation int,
118 defaultVal ldvalue.Value,
119 reason ldreason.EvaluationReason,
120 ) {
121 expectedEvent := ldevents.EvaluationData{
122 BaseEvent: ldevents.BaseEvent{
123 CreationDate: actualEvent.CreationDate,
124 Context: ldevents.Context(user),
125 },
126 Key: flagKey,
127 Version: ldvalue.NewOptionalInt(flagVersion),
128 Value: value,
129 Variation: ldvalue.NewOptionalInt(variation),
130 Default: defaultVal,
131 Reason: reason,
132 }
133 assert.Equal(t, expectedEvent, actualEvent)
134 }
135
136 func TestBoolVariation(t *testing.T) {
137 expected, defaultVal := true, false
138
139 t.Run("simple", func(t *testing.T) {
140 withClientEvalTestParams(func(p clientEvalTestParams) {
141 p.setupSingleValueFlag(evalFlagKey, ldvalue.Bool(true))
142
143 actual, err := p.client.BoolVariation(evalFlagKey, evalTestUser, defaultVal)
144
145 assert.NoError(t, err)
146 assert.Equal(t, expected, actual)
147
148 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.Bool(expected), ldvalue.Bool(defaultVal), noReason)
149 })
150 })
151
152 t.Run("detail", func(t *testing.T) {
153 withClientEvalTestParams(func(p clientEvalTestParams) {
154 p.setupSingleValueFlag(evalFlagKey, ldvalue.Bool(true))
155
156 actual, detail, err := p.client.BoolVariationDetail(evalFlagKey, evalTestUser, defaultVal)
157
158 assert.NoError(t, err)
159 assert.Equal(t, expected, actual)
160 assert.Equal(t, ldreason.NewEvaluationDetail(ldvalue.Bool(expected), expectedVariationForSingleValueFlag,
161 expectedReasonForSingleValueFlag), detail)
162
163 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.Bool(expected), ldvalue.Bool(defaultVal), detail.Reason)
164 })
165 })
166 }
167
168 func TestIntVariation(t *testing.T) {
169 expected, defaultVal := 100, 10000
170
171 t.Run("simple", func(t *testing.T) {
172 withClientEvalTestParams(func(p clientEvalTestParams) {
173 p.setupSingleValueFlag(evalFlagKey, ldvalue.Int(expected))
174
175 actual, err := p.client.IntVariation(evalFlagKey, evalTestUser, defaultVal)
176
177 assert.NoError(t, err)
178 assert.Equal(t, expected, actual)
179
180 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.Int(expected), ldvalue.Int(defaultVal), noReason)
181 })
182 })
183
184 t.Run("detail", func(t *testing.T) {
185 withClientEvalTestParams(func(p clientEvalTestParams) {
186 p.setupSingleValueFlag(evalFlagKey, ldvalue.Int(expected))
187
188 actual, detail, err := p.client.IntVariationDetail(evalFlagKey, evalTestUser, defaultVal)
189
190 assert.NoError(t, err)
191 assert.Equal(t, expected, actual)
192 assert.Equal(t, ldreason.NewEvaluationDetail(ldvalue.Int(expected), expectedVariationForSingleValueFlag,
193 expectedReasonForSingleValueFlag), detail)
194
195 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.Int(expected), ldvalue.Int(defaultVal), detail.Reason)
196 })
197 })
198
199 t.Run("rounds float toward zero", func(t *testing.T) {
200 flag1Key, flag2Key, flag3Key, flag4Key := "flag1", "flag2", "flag3", "flag4"
201 withClientEvalTestParams(func(p clientEvalTestParams) {
202 p.setupSingleValueFlag(flag1Key, ldvalue.Float64(2.25))
203 p.setupSingleValueFlag(flag2Key, ldvalue.Float64(2.75))
204 p.setupSingleValueFlag(flag3Key, ldvalue.Float64(-2.25))
205 p.setupSingleValueFlag(flag4Key, ldvalue.Float64(-2.75))
206
207 actual, err := p.client.IntVariation(flag1Key, evalTestUser, 0)
208 assert.NoError(t, err)
209 assert.Equal(t, 2, actual)
210
211 actual, err = p.client.IntVariation(flag2Key, evalTestUser, 0)
212 assert.NoError(t, err)
213 assert.Equal(t, 2, actual)
214
215 actual, err = p.client.IntVariation(flag3Key, evalTestUser, 0)
216 assert.NoError(t, err)
217 assert.Equal(t, -2, actual)
218
219 actual, err = p.client.IntVariation(flag4Key, evalTestUser, 0)
220 assert.NoError(t, err)
221 assert.Equal(t, -2, actual)
222 })
223 })
224 }
225
226 func TestFloat64Variation(t *testing.T) {
227 expected, defaultVal := 100.01, 0.0
228
229 t.Run("simple", func(t *testing.T) {
230 withClientEvalTestParams(func(p clientEvalTestParams) {
231 p.setupSingleValueFlag(evalFlagKey, ldvalue.Float64(expected))
232
233 actual, err := p.client.Float64Variation(evalFlagKey, evalTestUser, defaultVal)
234
235 assert.NoError(t, err)
236 assert.Equal(t, expected, actual)
237
238 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.Float64(expected), ldvalue.Float64(defaultVal), noReason)
239 })
240 })
241
242 t.Run("detail", func(t *testing.T) {
243 withClientEvalTestParams(func(p clientEvalTestParams) {
244 p.setupSingleValueFlag(evalFlagKey, ldvalue.Float64(expected))
245
246 actual, detail, err := p.client.Float64VariationDetail(evalFlagKey, evalTestUser, defaultVal)
247
248 assert.NoError(t, err)
249 assert.Equal(t, expected, actual)
250 assert.Equal(t, ldreason.NewEvaluationDetail(ldvalue.Float64(expected), expectedVariationForSingleValueFlag,
251 expectedReasonForSingleValueFlag), detail)
252
253 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.Float64(expected), ldvalue.Float64(defaultVal), detail.Reason)
254 })
255 })
256 }
257
258 func TestStringVariation(t *testing.T) {
259 expected, defaultVal := "b", "a"
260
261 t.Run("simple", func(t *testing.T) {
262 withClientEvalTestParams(func(p clientEvalTestParams) {
263 p.setupSingleValueFlag(evalFlagKey, ldvalue.String(expected))
264
265 actual, err := p.client.StringVariation(evalFlagKey, evalTestUser, defaultVal)
266
267 assert.NoError(t, err)
268 assert.Equal(t, expected, actual)
269
270 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.String(expected), ldvalue.String(defaultVal), noReason)
271 })
272 })
273
274 t.Run("detail", func(t *testing.T) {
275 withClientEvalTestParams(func(p clientEvalTestParams) {
276 p.setupSingleValueFlag(evalFlagKey, ldvalue.String(expected))
277
278 actual, detail, err := p.client.StringVariationDetail(evalFlagKey, evalTestUser, defaultVal)
279
280 assert.NoError(t, err)
281 assert.Equal(t, expected, actual)
282 assert.Equal(t, ldreason.NewEvaluationDetail(ldvalue.String(expected), expectedVariationForSingleValueFlag,
283 expectedReasonForSingleValueFlag), detail)
284
285 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.String(expected), ldvalue.String(defaultVal), detail.Reason)
286 })
287 })
288 }
289
290 func TestJSONRawVariation(t *testing.T) {
291 expectedValue := map[string]interface{}{"field2": "value2"}
292 expectedJSON, _ := json.Marshal(expectedValue)
293 expectedRaw := json.RawMessage(expectedJSON)
294 defaultVal := json.RawMessage([]byte(`{"default":"default"}`))
295
296 t.Run("simple", func(t *testing.T) {
297 withClientEvalTestParams(func(p clientEvalTestParams) {
298 p.setupSingleValueFlag(evalFlagKey, ldvalue.CopyArbitraryValue(expectedValue))
299
300 actual, err := p.client.JSONVariation(evalFlagKey, evalTestUser, ldvalue.Raw(defaultVal))
301
302 assert.NoError(t, err)
303 assert.Equal(t, expectedRaw, actual.AsRaw())
304
305 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.CopyArbitraryValue(expectedValue),
306 ldvalue.CopyArbitraryValue(defaultVal), noReason)
307 })
308 })
309
310 t.Run("detail", func(t *testing.T) {
311 withClientEvalTestParams(func(p clientEvalTestParams) {
312 p.setupSingleValueFlag(evalFlagKey, ldvalue.CopyArbitraryValue(expectedValue))
313
314 actual, detail, err := p.client.JSONVariationDetail(evalFlagKey, evalTestUser, ldvalue.Raw(defaultVal))
315
316 assert.NoError(t, err)
317 assert.Equal(t, expectedRaw, actual.AsRaw())
318 assert.Equal(t, ldreason.NewEvaluationDetail(ldvalue.Parse(expectedRaw), expectedVariationForSingleValueFlag,
319 expectedReasonForSingleValueFlag), detail)
320
321 p.expectSingleEvaluationEvent(t, evalFlagKey, ldvalue.CopyArbitraryValue(expectedValue),
322 ldvalue.CopyArbitraryValue(defaultVal), detail.Reason)
323 })
324 })
325 }
326
327 func TestJSONVariation(t *testing.T) {
328 expected := ldvalue.CopyArbitraryValue(map[string]interface{}{"field2": "value2"})
329 defaultVal := ldvalue.String("no")
330
331 t.Run("simple", func(t *testing.T) {
332 withClientEvalTestParams(func(p clientEvalTestParams) {
333 p.setupSingleValueFlag(evalFlagKey, expected)
334
335 actual, err := p.client.JSONVariation(evalFlagKey, evalTestUser, defaultVal)
336
337 assert.NoError(t, err)
338 assert.Equal(t, expected, actual)
339
340 p.expectSingleEvaluationEvent(t, evalFlagKey, expected, defaultVal, noReason)
341 })
342 })
343
344 t.Run("detail", func(t *testing.T) {
345 withClientEvalTestParams(func(p clientEvalTestParams) {
346 p.setupSingleValueFlag(evalFlagKey, expected)
347
348 actual, detail, err := p.client.JSONVariationDetail(evalFlagKey, evalTestUser, defaultVal)
349
350 assert.NoError(t, err)
351 assert.Equal(t, expected, actual)
352 assert.Equal(t, ldreason.NewEvaluationDetail(expected, expectedVariationForSingleValueFlag,
353 expectedReasonForSingleValueFlag), detail)
354
355 p.expectSingleEvaluationEvent(t, evalFlagKey, expected, defaultVal, detail.Reason)
356 })
357 })
358 }
359
360 func TestEvaluatingUnknownFlagReturnsDefault(t *testing.T) {
361 withClientEvalTestParams(func(p clientEvalTestParams) {
362 value, err := p.client.StringVariation("no-such-flag", evalTestUser, "default")
363 assert.Error(t, err)
364 assert.Equal(t, "default", value)
365 })
366 }
367
368 func TestEvaluatingUnknownFlagReturnsDefaultWithDetail(t *testing.T) {
369 withClientEvalTestParams(func(p clientEvalTestParams) {
370 _, detail, err := p.client.StringVariationDetail("no-such-flag", evalTestUser, "default")
371 assert.Error(t, err)
372 assert.Equal(t, ldvalue.String("default"), detail.Value)
373 assert.Equal(t, ldvalue.OptionalInt{}, detail.VariationIndex)
374 assert.Equal(t, ldreason.NewEvalReasonError(ldreason.EvalErrorFlagNotFound), detail.Reason)
375 assert.True(t, detail.IsDefaultValue())
376 })
377 }
378
379 func TestDefaultIsReturnedIfFlagEvaluatesToNil(t *testing.T) {
380 flag := ldbuilders.NewFlagBuilder(evalFlagKey).Build()
381
382 withClientEvalTestParams(func(p clientEvalTestParams) {
383 p.data.UsePreconfiguredFlag(flag)
384
385 value, err := p.client.StringVariation(evalFlagKey, evalTestUser, "default")
386 assert.NoError(t, err)
387 assert.Equal(t, "default", value)
388 })
389 }
390
391 func TestDefaultIsReturnedIfFlagEvaluatesToNilWithDetail(t *testing.T) {
392 flag := ldbuilders.NewFlagBuilder(evalFlagKey).Build()
393
394 withClientEvalTestParams(func(p clientEvalTestParams) {
395 p.data.UsePreconfiguredFlag(flag)
396
397 _, detail, err := p.client.StringVariationDetail(evalFlagKey, evalTestUser, "default")
398 assert.NoError(t, err)
399 assert.Equal(t, ldvalue.String("default"), detail.Value)
400 assert.Equal(t, ldvalue.OptionalInt{}, detail.VariationIndex)
401 assert.Equal(t, ldreason.NewEvalReasonOff(), detail.Reason)
402 })
403 }
404
405 func TestDefaultIsReturnedIfFlagReturnsWrongType(t *testing.T) {
406 withClientEvalTestParams(func(p clientEvalTestParams) {
407 p.setupSingleValueFlag(evalFlagKey, ldvalue.ArrayOf())
408
409 v1a, err1a := p.client.BoolVariation(evalFlagKey, evalTestUser, false)
410 v1b, detail1, err1b := p.client.BoolVariationDetail(evalFlagKey, evalTestUser, false)
411 assert.NoError(t, err1a)
412 assert.NoError(t, err1b)
413 assert.False(t, v1a)
414 assert.False(t, v1b)
415 assert.Equal(t, ldreason.EvalErrorWrongType, detail1.Reason.GetErrorKind())
416
417 v2a, err2a := p.client.IntVariation(evalFlagKey, evalTestUser, -1)
418 v2b, detail2, err2b := p.client.IntVariationDetail(evalFlagKey, evalTestUser, -1)
419 assert.NoError(t, err2a)
420 assert.NoError(t, err2b)
421 assert.Equal(t, -1, v2a)
422 assert.Equal(t, -1, v2b)
423 assert.Equal(t, ldreason.EvalErrorWrongType, detail2.Reason.GetErrorKind())
424
425 v3a, err3a := p.client.Float64Variation(evalFlagKey, evalTestUser, -1)
426 v3b, detail3, err3b := p.client.Float64VariationDetail(evalFlagKey, evalTestUser, -1)
427 assert.NoError(t, err3a)
428 assert.NoError(t, err3b)
429 assert.Equal(t, float64(-1), v3a)
430 assert.Equal(t, float64(-1), v3b)
431 assert.Equal(t, ldreason.EvalErrorWrongType, detail3.Reason.GetErrorKind())
432
433 v4a, err4a := p.client.StringVariation(evalFlagKey, evalTestUser, "x")
434 v4b, detail4, err4b := p.client.StringVariationDetail(evalFlagKey, evalTestUser, "x")
435 assert.NoError(t, err4a)
436 assert.NoError(t, err4b)
437 assert.Equal(t, "x", v4a)
438 assert.Equal(t, "x", v4b)
439 assert.Equal(t, ldreason.EvalErrorWrongType, detail4.Reason.GetErrorKind())
440 })
441 }
442
443 func TestEvaluateWithInvalidContext(t *testing.T) {
444 flagKey := "flag"
445 for _, contextParams := range []struct {
446 name string
447 context ldcontext.Context
448 errorText string
449 }{
450 {"empty key", ldcontext.New(""), "context key must not be empty"},
451 {"invalid kind", ldcontext.NewWithKind("!bad!", "key"), "context kind contains disallowed characters"},
452 } {
453 t.Run(contextParams.name, func(t *testing.T) {
454 c := contextParams.context
455 for _, evalFnParams := range []struct {
456 name string
457 fn func(*LDClient) error
458 }{
459 {"BoolVariation", func(client *LDClient) error { _, err := client.BoolVariation(flagKey, c, false); return err }},
460 {"IntVariation", func(client *LDClient) error { _, err := client.IntVariation(flagKey, c, 0); return err }},
461 {"Float64Variation", func(client *LDClient) error { _, err := client.Float64Variation(flagKey, c, 0); return err }},
462 {"StringVariation", func(client *LDClient) error { _, err := client.StringVariation(flagKey, c, ""); return err }},
463 {"JSONVariation", func(client *LDClient) error { _, err := client.JSONVariation(flagKey, c, ldvalue.Null()); return err }},
464 } {
465 t.Run(evalFnParams.name, func(t *testing.T) {
466 withClientEvalTestParams(func(p clientEvalTestParams) {
467 err := evalFnParams.fn(p.client)
468 assert.Error(t, err)
469 p.mockLog.AssertMessageMatch(t, true, ldlog.Warn, contextParams.errorText)
470 })
471 })
472 }
473 for _, evalFnParams := range []struct {
474 name string
475 fn func(*LDClient) (ldreason.EvaluationDetail, error)
476 }{
477 {"BoolVariationDetail",
478 func(client *LDClient) (ldreason.EvaluationDetail, error) {
479 _, detail, err := client.BoolVariationDetail(flagKey, c, false)
480 return detail, err
481 }},
482 {"IntVariationDetail",
483 func(client *LDClient) (ldreason.EvaluationDetail, error) {
484 _, detail, err := client.IntVariationDetail(flagKey, c, 0)
485 return detail, err
486 }},
487 {"Float64VariationDetail",
488 func(client *LDClient) (ldreason.EvaluationDetail, error) {
489 _, detail, err := client.Float64VariationDetail(flagKey, c, 0)
490 return detail, err
491 }},
492 {"StringVariationDetail", func(client *LDClient) (ldreason.EvaluationDetail, error) {
493 _, detail, err := client.StringVariationDetail(flagKey, c, "")
494 return detail, err
495 }},
496 {"JSONVariationDetail", func(client *LDClient) (ldreason.EvaluationDetail, error) {
497 _, detail, err := client.JSONVariationDetail(flagKey, c, ldvalue.Null())
498 return detail, err
499 }},
500 } {
501 t.Run(evalFnParams.name, func(t *testing.T) {
502 withClientEvalTestParams(func(p clientEvalTestParams) {
503 detail, err := evalFnParams.fn(p.client)
504 assert.Error(t, err)
505 assert.Equal(t, ldreason.NewEvalReasonError(ldreason.EvalErrorUserNotSpecified), detail.Reason)
506 p.mockLog.AssertMessageMatch(t, true, ldlog.Warn, contextParams.errorText)
507 })
508 })
509 }
510 })
511 }
512 }
513
514 func TestEventTrackingAndReasonCanBeForcedForRule(t *testing.T) {
515 flag := ldbuilders.NewFlagBuilder(evalFlagKey).
516 On(true).
517 AddRule(ldbuilders.NewRuleBuilder().
518 ID("rule-id").
519 Clauses(makeClauseToMatchUser(evalTestUser)).
520 Variation(1).
521 TrackEvents(true)).
522 Variations(offValue, onValue).
523 Version(1).
524 Build()
525
526 withClientEvalTestParams(func(p clientEvalTestParams) {
527 p.data.UsePreconfiguredFlag(flag)
528
529 value, err := p.client.StringVariation(evalFlagKey, evalTestUser, "default")
530 assert.NoError(t, err)
531 assert.Equal(t, "on", value)
532
533 e := p.requireSingleEvent(t)
534 assert.True(t, e.RequireFullEvent)
535 assert.Equal(t, ldreason.NewEvalReasonRuleMatch(0, "rule-id"), e.Reason)
536 })
537 }
538
539 func TestEventTrackingAndReasonAreNotForcedIfFlagIsNotSetForMatchingRule(t *testing.T) {
540 flag := ldbuilders.NewFlagBuilder(evalFlagKey).
541 On(true).
542 AddRule(ldbuilders.NewRuleBuilder().
543 ID("id0").
544 Clauses(makeClauseToNotMatchUser(evalTestUser)).
545 Variation(0).
546 TrackEvents(true)).
547 AddRule(ldbuilders.NewRuleBuilder().
548 ID("id1").
549 Clauses(makeClauseToMatchUser(evalTestUser)).
550 Variation(1)).
551 Variations(offValue, onValue).
552 Version(1).
553 Build()
554
555 withClientEvalTestParams(func(p clientEvalTestParams) {
556 p.data.UsePreconfiguredFlag(flag)
557
558 value, err := p.client.StringVariation(evalFlagKey, evalTestUser, "default")
559 assert.NoError(t, err)
560 assert.Equal(t, "on", value)
561
562 e := p.requireSingleEvent(t)
563 assert.False(t, e.RequireFullEvent)
564 assert.Equal(t, ldreason.EvaluationReason{}, e.Reason)
565 })
566 }
567
568 func TestEventTrackingAndReasonCanBeForcedForFallthrough(t *testing.T) {
569 flag := ldbuilders.NewFlagBuilder(evalFlagKey).
570 On(true).
571 FallthroughVariation(1).
572 Variations(offValue, onValue).
573 TrackEventsFallthrough(true).
574 Version(1).
575 Build()
576
577 withClientEvalTestParams(func(p clientEvalTestParams) {
578 p.data.UsePreconfiguredFlag(flag)
579
580 value, err := p.client.StringVariation(evalFlagKey, evalTestUser, "default")
581 assert.NoError(t, err)
582 assert.Equal(t, "on", value)
583
584 e := p.requireSingleEvent(t)
585 assert.True(t, e.RequireFullEvent)
586 assert.Equal(t, ldreason.NewEvalReasonFallthrough(), e.Reason)
587 })
588 }
589
590 func TestEventTrackingAndReasonAreNotForcedForFallthroughIfFlagIsNotSet(t *testing.T) {
591 flag := ldbuilders.NewFlagBuilder(evalFlagKey).
592 On(true).
593 FallthroughVariation(1).
594 Variations(offValue, onValue).
595 Version(1).
596 Build()
597
598 withClientEvalTestParams(func(p clientEvalTestParams) {
599 p.data.UsePreconfiguredFlag(flag)
600
601 value, err := p.client.StringVariation(evalFlagKey, evalTestUser, "default")
602 assert.NoError(t, err)
603 assert.Equal(t, "on", value)
604
605 e := p.requireSingleEvent(t)
606 assert.False(t, e.RequireFullEvent)
607 assert.Equal(t, ldreason.EvaluationReason{}, e.Reason)
608 })
609 }
610
611 func TestEventTrackingAndReasonAreNotForcedForFallthroughIfReasonIsNotFallthrough(t *testing.T) {
612 withClientEvalTestParams(func(p clientEvalTestParams) {
613 p.data.Update(p.data.Flag(evalFlagKey).Variations(offValue, onValue).OffVariationIndex(0).On(false))
614
615 value, err := p.client.StringVariation(evalFlagKey, evalTestUser, "default")
616 assert.NoError(t, err)
617 assert.Equal(t, "off", value)
618
619 e := p.requireSingleEvent(t)
620 assert.False(t, e.RequireFullEvent)
621 assert.Equal(t, ldreason.EvaluationReason{}, e.Reason)
622 })
623 }
624
625 func TestEvaluatingUnknownFlagSendsEvent(t *testing.T) {
626 withClientEvalTestParams(func(p clientEvalTestParams) {
627 _, err := p.client.StringVariation("no-such-flag", evalTestUser, "x")
628 assert.Error(t, err)
629
630 e := p.requireSingleEvent(t)
631 expectedEvent := ldevents.EvaluationData{
632 BaseEvent: ldevents.BaseEvent{
633 CreationDate: e.CreationDate,
634 Context: ldevents.Context(evalTestUser),
635 },
636 Key: "no-such-flag",
637 Value: ldvalue.String("x"),
638 Default: ldvalue.String("x"),
639 }
640 assert.Equal(t, expectedEvent, e)
641 })
642 }
643
644 func TestEvaluatingFlagWithPrerequisiteSendsPrerequisiteEvent(t *testing.T) {
645 flag0 := ldbuilders.NewFlagBuilder("flag0").
646 On(true).
647 FallthroughVariation(1).
648 Variations(ldvalue.String("a"), ldvalue.String("b")).
649 AddPrerequisite("flag1", 1).
650 Build()
651 flag1 := ldbuilders.NewFlagBuilder("flag1").
652 On(true).
653 FallthroughVariation(1).
654 Variations(ldvalue.String("c"), ldvalue.String("d")).
655 Build()
656
657 withClientEvalTestParams(func(p clientEvalTestParams) {
658 p.data.UsePreconfiguredFlag(flag0)
659 p.data.UsePreconfiguredFlag(flag1)
660
661 user := lduser.NewUser("userKey")
662 _, err := p.client.StringVariation(flag0.Key, user, "x")
663 assert.NoError(t, err)
664
665 events := p.events.Events
666 assert.Len(t, events, 2)
667 e0 := events[0].(ldevents.EvaluationData)
668 expected0 := ldevents.EvaluationData{
669 BaseEvent: ldevents.BaseEvent{
670 CreationDate: e0.CreationDate,
671 Context: ldevents.Context(user),
672 },
673 Key: flag1.Key,
674 Version: ldvalue.NewOptionalInt(1),
675 Value: ldvalue.String("d"),
676 Variation: ldvalue.NewOptionalInt(1),
677 Default: ldvalue.Null(),
678 PrereqOf: ldvalue.NewOptionalString(flag0.Key),
679 }
680 assert.Equal(t, expected0, e0)
681
682 e1 := events[1].(ldevents.EvaluationData)
683 expected1 := ldevents.EvaluationData{
684 BaseEvent: ldevents.BaseEvent{
685 CreationDate: e1.CreationDate,
686 Context: ldevents.Context(user),
687 },
688 Key: flag0.Key,
689 Version: ldvalue.NewOptionalInt(1),
690 Value: ldvalue.String("b"),
691 Variation: ldvalue.NewOptionalInt(1),
692 Default: ldvalue.String("x"),
693 }
694 assert.Equal(t, expected1, e1)
695 })
696 }
697
698 func TestEvalErrorIfStoreReturnsError(t *testing.T) {
699 myError := errors.New("sorry")
700 store := mocks.NewCapturingDataStore(datastore.NewInMemoryDataStore(sharedtest.NewTestLoggers()))
701 _ = store.Init(nil)
702 store.SetFakeError(myError)
703 client := makeTestClientWithConfig(func(c *Config) {
704 c.DataStore = mocks.SingleComponentConfigurer[subsystems.DataStore]{Instance: store}
705 })
706 defer client.Close()
707
708 value, err := client.BoolVariation("flag", evalTestUser, false)
709 assert.False(t, value)
710 assert.Equal(t, myError, err)
711 }
712
713 func TestEvalErrorIfStoreHasNonFlagObject(t *testing.T) {
714 key := "not-really-a-flag"
715 notAFlag := 9
716
717 withClientEvalTestParams(func(p clientEvalTestParams) {
718 p.store.Upsert(datakinds.Features, key,
719 ldstoretypes.ItemDescriptor{Version: 1, Item: notAFlag})
720
721 value, err := p.client.BoolVariation(key, evalTestUser, false)
722 assert.False(t, value)
723 assert.Error(t, err)
724 })
725 }
726
727 func TestUnknownFlagErrorLogging(t *testing.T) {
728 testEvalErrorLogging(t, nil, "unknown-flag", evalTestUser,
729 "",
730 "unknown feature key: unknown-flag\\. Verify that this feature key exists\\. Returning default value")
731 }
732
733 func TestMalformedFlagErrorLogging(t *testing.T) {
734 flag := ldbuilders.NewFlagBuilder("bad-flag").On(false).OffVariation(99).Build()
735 testEvalErrorLogging(t, &flag, "", evalTestUser,
736 `Invalid flag configuration.*"bad-flag".*nonexistent variation index 99`,
737 "Flag evaluation for bad-flag failed with error MALFORMED_FLAG, default value was returned")
738 }
739
740 func testEvalErrorLogging(t *testing.T, flag *ldmodel.FeatureFlag, key string, user ldcontext.Context,
741 expectedErrorRegex, expectedWarningRegex string) {
742 runTest := func(withLogging bool) {
743 mockLoggers := ldlogtest.NewMockLog()
744 testData := ldtestdata.DataSource()
745 client := makeTestClientWithConfig(func(c *Config) {
746 c.DataSource = testData
747 c.Logging = ldcomponents.Logging().Loggers(mockLoggers.Loggers).MinLevel(ldlog.Warn).LogEvaluationErrors(withLogging)
748 })
749 defer client.Close()
750 if flag != nil {
751 testData.UsePreconfiguredFlag(*flag)
752 key = flag.Key
753 }
754
755 value, _ := client.StringVariation(key, user, "default")
756 assert.Equal(t, "default", value)
757
758 if expectedErrorRegex == "" {
759 require.Len(t, mockLoggers.GetOutput(ldlog.Error), 0)
760 } else {
761 require.Len(t, mockLoggers.GetOutput(ldlog.Error), 1)
762 assert.Regexp(t, expectedErrorRegex, mockLoggers.GetOutput(ldlog.Error)[0])
763 }
764
765 if withLogging {
766 require.Len(t, mockLoggers.GetOutput(ldlog.Warn), 1)
767 assert.Regexp(t, expectedWarningRegex, mockLoggers.GetOutput(ldlog.Warn)[0])
768 } else {
769 assert.Len(t, mockLoggers.GetOutput(ldlog.Warn), 0)
770 }
771 }
772 runTest(false)
773 runTest(true)
774 }
775
776 func TestEvalReturnsDefaultIfClientAndStoreAreNotInitialized(t *testing.T) {
777 mockLoggers := ldlogtest.NewMockLog()
778
779 client := makeTestClientWithConfig(func(c *Config) {
780 c.DataSource = mocks.DataSourceThatNeverInitializes()
781 c.Logging = ldcomponents.Logging().Loggers(mockLoggers.Loggers)
782 })
783 defer client.Close()
784
785 value, err := client.BoolVariation("flagkey", evalTestUser, false)
786 require.Error(t, err)
787 assert.Equal(t, "feature flag evaluation called before LaunchDarkly client initialization completed",
788 err.Error())
789 assert.False(t, value)
790
791 assert.Len(t, mockLoggers.GetOutput(ldlog.Warn), 0)
792 }
793
794 func TestEvalUsesStoreAndLogsWarningIfClientIsNotInitializedButStoreIsInitialized(t *testing.T) {
795 mockLoggers := ldlogtest.NewMockLog()
796 flag := ldbuilders.NewFlagBuilder(evalFlagKey).SingleVariation(ldvalue.Bool(true)).Build()
797 store := datastore.NewInMemoryDataStore(sharedtest.NewTestLoggers())
798 _ = store.Init(nil)
799 _, _ = store.Upsert(datakinds.Features, flag.Key, sharedtest.FlagDescriptor(flag))
800
801 client := makeTestClientWithConfig(func(c *Config) {
802 c.DataSource = mocks.DataSourceThatNeverInitializes()
803 c.DataStore = mocks.SingleComponentConfigurer[subsystems.DataStore]{Instance: store}
804 c.Logging = ldcomponents.Logging().Loggers(mockLoggers.Loggers)
805 })
806 defer client.Close()
807
808 value, err := client.BoolVariation(flag.Key, evalTestUser, false)
809 assert.NoError(t, err)
810 assert.True(t, value)
811
812 assert.Len(t, mockLoggers.GetOutput(ldlog.Warn), 1)
813 assert.Contains(t, mockLoggers.GetOutput(ldlog.Warn)[0], "using last known values")
814 }
815
View as plain text