...

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

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

     1  package ldclient
     2  
     3  import (
     4  	"errors"
     5  	"testing"
     6  
     7  	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks"
     8  
     9  	"github.com/launchdarkly/go-sdk-common/v3/ldlog"
    10  	"github.com/launchdarkly/go-sdk-common/v3/ldlogtest"
    11  	"github.com/launchdarkly/go-sdk-common/v3/ldreason"
    12  	"github.com/launchdarkly/go-sdk-common/v3/ldtime"
    13  	"github.com/launchdarkly/go-sdk-common/v3/lduser"
    14  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
    15  	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
    16  	"github.com/launchdarkly/go-server-sdk/v6/interfaces/flagstate"
    17  	"github.com/launchdarkly/go-server-sdk/v6/internal/datakinds"
    18  	"github.com/launchdarkly/go-server-sdk/v6/internal/datastore"
    19  	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest"
    20  	"github.com/launchdarkly/go-server-sdk/v6/ldcomponents"
    21  	"github.com/launchdarkly/go-server-sdk/v6/subsystems"
    22  
    23  	"github.com/stretchr/testify/assert"
    24  )
    25  
    26  func TestAllFlagsStateGetsState(t *testing.T) {
    27  	flag1 := ldbuilders.NewFlagBuilder("key1").Version(100).OffVariation(0).
    28  		Variations(ldvalue.String("value1")).Build()
    29  	flag2 := ldbuilders.NewFlagBuilder("key2").Version(200).OffVariation(1).
    30  		Variations(ldvalue.String("x"), ldvalue.String("value2")).
    31  		TrackEvents(true).DebugEventsUntilDate(1000).Build()
    32  
    33  	// flag3 has an experiment (evaluation is a fallthrough and TrackEventsFallthrough is on)
    34  	flag3 := ldbuilders.NewFlagBuilder("key3").Version(300).On(true).FallthroughVariation(1).
    35  		Variations(ldvalue.String("x"), ldvalue.String("value3")).
    36  		TrackEvents(false).TrackEventsFallthrough(true).Build()
    37  
    38  	withClientEvalTestParams(func(p clientEvalTestParams) {
    39  		p.data.UsePreconfiguredFlag(flag1)
    40  		p.data.UsePreconfiguredFlag(flag2)
    41  		p.data.UsePreconfiguredFlag(flag3)
    42  
    43  		state := p.client.AllFlagsState(lduser.NewUser("userkey"))
    44  		assert.True(t, state.IsValid())
    45  
    46  		expected := flagstate.NewAllFlagsBuilder().
    47  			AddFlag("key1", flagstate.FlagState{
    48  				Value:     ldvalue.String("value1"),
    49  				Variation: ldvalue.NewOptionalInt(0),
    50  				Version:   100,
    51  			}).
    52  			AddFlag("key2", flagstate.FlagState{
    53  				Value:                ldvalue.String("value2"),
    54  				Variation:            ldvalue.NewOptionalInt(1),
    55  				Version:              200,
    56  				TrackEvents:          true,
    57  				DebugEventsUntilDate: ldtime.UnixMillisecondTime(1000),
    58  			}).
    59  			AddFlag("key3", flagstate.FlagState{
    60  				Value:       ldvalue.String("value3"),
    61  				Variation:   ldvalue.NewOptionalInt(1),
    62  				Version:     300,
    63  				Reason:      ldreason.NewEvalReasonFallthrough(),
    64  				TrackEvents: true,
    65  				TrackReason: true,
    66  			}).
    67  			Build()
    68  		assert.Equal(t, expected, state)
    69  	})
    70  }
    71  
    72  func TestAllFlagsStateGetsStateWithReasons(t *testing.T) {
    73  	flag1 := ldbuilders.NewFlagBuilder("key1").Version(100).On(false).OffVariation(0).
    74  		Variations(ldvalue.String("value1")).Build()
    75  	flag2 := ldbuilders.NewFlagBuilder("key2").Version(200).OffVariation(1).
    76  		Variations(ldvalue.String("x"), ldvalue.String("value2")).
    77  		TrackEvents(true).DebugEventsUntilDate(1000).Build()
    78  
    79  	withClientEvalTestParams(func(p clientEvalTestParams) {
    80  		p.data.UsePreconfiguredFlag(flag1)
    81  		p.data.UsePreconfiguredFlag(flag2)
    82  
    83  		state := p.client.AllFlagsState(lduser.NewUser("userkey"), flagstate.OptionWithReasons())
    84  		assert.True(t, state.IsValid())
    85  
    86  		expected := flagstate.NewAllFlagsBuilder(flagstate.OptionWithReasons()).
    87  			AddFlag("key1", flagstate.FlagState{
    88  				Value:     ldvalue.String("value1"),
    89  				Variation: ldvalue.NewOptionalInt(0),
    90  				Version:   100,
    91  				Reason:    ldreason.NewEvalReasonOff(),
    92  			}).
    93  			AddFlag("key2", flagstate.FlagState{
    94  				Value:                ldvalue.String("value2"),
    95  				Variation:            ldvalue.NewOptionalInt(1),
    96  				Version:              200,
    97  				Reason:               ldreason.NewEvalReasonOff(),
    98  				TrackEvents:          true,
    99  				DebugEventsUntilDate: ldtime.UnixMillisecondTime(1000),
   100  			}).
   101  			Build()
   102  		assert.Equal(t, expected, state)
   103  	})
   104  }
   105  
   106  func TestAllFlagsStateCanFilterForOnlyClientSideFlags(t *testing.T) {
   107  	flag1 := ldbuilders.NewFlagBuilder("server-side-1").Build()
   108  	flag2 := ldbuilders.NewFlagBuilder("server-side-2").Build()
   109  	flag3 := ldbuilders.NewFlagBuilder("client-side-1").SingleVariation(ldvalue.String("value1")).
   110  		ClientSideUsingEnvironmentID(true).Build()
   111  	flag4 := ldbuilders.NewFlagBuilder("client-side-2").SingleVariation(ldvalue.String("value2")).
   112  		ClientSideUsingEnvironmentID(true).Build()
   113  
   114  	withClientEvalTestParams(func(p clientEvalTestParams) {
   115  		p.data.UsePreconfiguredFlag(flag1)
   116  		p.data.UsePreconfiguredFlag(flag2)
   117  		p.data.UsePreconfiguredFlag(flag3)
   118  		p.data.UsePreconfiguredFlag(flag4)
   119  
   120  		state := p.client.AllFlagsState(lduser.NewUser("userkey"), flagstate.OptionClientSideOnly())
   121  		assert.True(t, state.IsValid())
   122  
   123  		expectedValues := map[string]ldvalue.Value{"client-side-1": ldvalue.String("value1"), "client-side-2": ldvalue.String("value2")}
   124  		assert.Equal(t, expectedValues, state.ToValuesMap())
   125  	})
   126  }
   127  
   128  func TestAllFlagsStateCanOmitDetailForUntrackedFlags(t *testing.T) {
   129  	futureTime := ldtime.UnixMillisNow() + 100000
   130  
   131  	// flag1 does not get full detials because neither event tracking nor debugging is on and there's no experiment
   132  	flag1 := ldbuilders.NewFlagBuilder("key1").Version(100).OffVariation(0).Variations(ldvalue.String("value1")).Build()
   133  
   134  	// flag2 gets full details because event tracking is on
   135  	flag2 := ldbuilders.NewFlagBuilder("key2").Version(200).OffVariation(1).Variations(ldvalue.String("x"), ldvalue.String("value2")).
   136  		TrackEvents(true).Build()
   137  
   138  	// flag3 gets full details because debugging is on
   139  	flag3 := ldbuilders.NewFlagBuilder("key3").Version(300).OffVariation(1).Variations(ldvalue.String("x"), ldvalue.String("value3")).
   140  		TrackEvents(false).DebugEventsUntilDate(futureTime).Build()
   141  
   142  	// flag4 gets full details because there's an experiment (evaluation is a fallthrough and TrackEventsFallthrough is on)
   143  	flag4 := ldbuilders.NewFlagBuilder("key4").Version(400).On(true).FallthroughVariation(1).
   144  		Variations(ldvalue.String("x"), ldvalue.String("value4")).
   145  		TrackEvents(false).TrackEventsFallthrough(true).Build()
   146  
   147  	withClientEvalTestParams(func(p clientEvalTestParams) {
   148  		p.data.UsePreconfiguredFlag(flag1)
   149  		p.data.UsePreconfiguredFlag(flag2)
   150  		p.data.UsePreconfiguredFlag(flag3)
   151  		p.data.UsePreconfiguredFlag(flag4)
   152  
   153  		state := p.client.AllFlagsState(lduser.NewUser("userkey"), flagstate.OptionWithReasons(),
   154  			flagstate.OptionDetailsOnlyForTrackedFlags())
   155  		assert.True(t, state.IsValid())
   156  
   157  		expected := flagstate.NewAllFlagsBuilder(flagstate.OptionWithReasons()).
   158  			AddFlag("key1", flagstate.FlagState{
   159  				Value:       ldvalue.String("value1"),
   160  				Variation:   ldvalue.NewOptionalInt(0),
   161  				Version:     100,
   162  				Reason:      ldreason.NewEvalReasonOff(),
   163  				OmitDetails: true,
   164  			}).
   165  			AddFlag("key2", flagstate.FlagState{
   166  				Value:       ldvalue.String("value2"),
   167  				Variation:   ldvalue.NewOptionalInt(1),
   168  				Version:     200,
   169  				Reason:      ldreason.NewEvalReasonOff(),
   170  				TrackEvents: true,
   171  			}).
   172  			AddFlag("key3", flagstate.FlagState{
   173  				Value:                ldvalue.String("value3"),
   174  				Variation:            ldvalue.NewOptionalInt(1),
   175  				Version:              300,
   176  				Reason:               ldreason.NewEvalReasonOff(),
   177  				DebugEventsUntilDate: futureTime,
   178  			}).
   179  			AddFlag("key4", flagstate.FlagState{
   180  				Value:       ldvalue.String("value4"),
   181  				Variation:   ldvalue.NewOptionalInt(1),
   182  				Version:     400,
   183  				Reason:      ldreason.NewEvalReasonFallthrough(),
   184  				TrackEvents: true,
   185  				TrackReason: true,
   186  			}).
   187  			Build()
   188  		assert.Equal(t, expected, state)
   189  	})
   190  }
   191  
   192  func TestAllFlagsStateReturnsInvalidStateIfClientAndStoreAreNotInitialized(t *testing.T) {
   193  	mockLoggers := ldlogtest.NewMockLog()
   194  
   195  	client := makeTestClientWithConfig(func(c *Config) {
   196  		c.DataSource = mocks.DataSourceThatNeverInitializes()
   197  		c.Logging = ldcomponents.Logging().Loggers(mockLoggers.Loggers)
   198  	})
   199  	defer client.Close()
   200  
   201  	state := client.AllFlagsState(evalTestUser)
   202  	assert.False(t, state.IsValid())
   203  	assert.Len(t, state.ToValuesMap(), 0)
   204  }
   205  
   206  func TestAllFlagsStateUsesStoreAndLogsWarningIfClientIsNotInitializedButStoreIsInitialized(t *testing.T) {
   207  	mockLoggers := ldlogtest.NewMockLog()
   208  	flag := ldbuilders.NewFlagBuilder(evalFlagKey).SingleVariation(ldvalue.Bool(true)).Build()
   209  	store := datastore.NewInMemoryDataStore(sharedtest.NewTestLoggers())
   210  	_ = store.Init(nil)
   211  	_, _ = store.Upsert(datakinds.Features, flag.Key, sharedtest.FlagDescriptor(flag))
   212  
   213  	client := makeTestClientWithConfig(func(c *Config) {
   214  		c.DataSource = mocks.DataSourceThatNeverInitializes()
   215  		c.DataStore = mocks.SingleComponentConfigurer[subsystems.DataStore]{Instance: store}
   216  		c.Logging = ldcomponents.Logging().Loggers(mockLoggers.Loggers)
   217  	})
   218  	defer client.Close()
   219  
   220  	state := client.AllFlagsState(evalTestUser)
   221  	assert.True(t, state.IsValid())
   222  	assert.Len(t, state.ToValuesMap(), 1)
   223  
   224  	assert.Len(t, mockLoggers.GetOutput(ldlog.Warn), 1)
   225  	assert.Contains(t, mockLoggers.GetOutput(ldlog.Warn)[0], "using last known values")
   226  }
   227  
   228  func TestAllFlagsStateReturnsInvalidStateIfStoreReturnsError(t *testing.T) {
   229  	myError := errors.New("sorry")
   230  	store := mocks.NewCapturingDataStore(datastore.NewInMemoryDataStore(sharedtest.NewTestLoggers()))
   231  	_ = store.Init(nil)
   232  	store.SetFakeError(myError)
   233  	mockLoggers := ldlogtest.NewMockLog()
   234  
   235  	client := makeTestClientWithConfig(func(c *Config) {
   236  		c.DataSource = mocks.DataSourceThatIsAlwaysInitialized()
   237  		c.DataStore = mocks.SingleComponentConfigurer[subsystems.DataStore]{Instance: store}
   238  		c.Logging = ldcomponents.Logging().Loggers(mockLoggers.Loggers)
   239  	})
   240  	defer client.Close()
   241  
   242  	state := client.AllFlagsState(evalTestUser)
   243  	assert.False(t, state.IsValid())
   244  	assert.Len(t, state.ToValuesMap(), 0)
   245  
   246  	assert.Len(t, mockLoggers.GetOutput(ldlog.Warn), 1)
   247  	assert.Contains(t, mockLoggers.GetOutput(ldlog.Warn)[0], "Unable to fetch flags")
   248  }
   249  

View as plain text