...

Source file src/github.com/launchdarkly/go-server-sdk/v6/ldclient_evaluation_test.go

Documentation: github.com/launchdarkly/go-server-sdk/v6

     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  		// We add some unused variations so that the result variation index won't be zero, since it's always
    66  		// hard to tell if a zero is an intentional result or just an uninitialized variable.
    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() // flag is off and we haven't defined an off variation
   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() // flag is off and we haven't defined an off variation
   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