package ldtestdata import ( "testing" "time" "github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks" "github.com/launchdarkly/go-sdk-common/v3/ldvalue" "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders" "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel" "github.com/launchdarkly/go-server-sdk/v6/interfaces" "github.com/launchdarkly/go-server-sdk/v6/internal/datastore" "github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest" "github.com/launchdarkly/go-server-sdk/v6/subsystems" "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoreimpl" "github.com/launchdarkly/go-server-sdk/v6/testhelpers/ldservices" th "github.com/launchdarkly/go-test-helpers/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var ( threeStringValues = []ldvalue.Value{ldvalue.String("red"), ldvalue.String("green"), ldvalue.String("blue")} ) type testDataSourceTestParams struct { td *TestDataSource updates *mocks.MockDataSourceUpdates } func testDataSourceTest(t *testing.T, action func(testDataSourceTestParams)) { t.Helper() store := datastore.NewInMemoryDataStore(sharedtest.NewTestLoggers()) var p testDataSourceTestParams p.td = DataSource() p.updates = mocks.NewMockDataSourceUpdates(store) action(p) } func (p testDataSourceTestParams) withDataSource(t *testing.T, action func(subsystems.DataSource)) { t.Helper() context := subsystems.BasicClientContext{DataSourceUpdateSink: p.updates} ds, err := p.td.Build(context) require.NoError(t, err) defer ds.Close() closer := make(chan struct{}) ds.Start(closer) if !th.AssertChannelClosed(t, closer, time.Millisecond, "start did not close channel") { t.FailNow() } p.updates.RequireStatusOf(t, interfaces.DataSourceStateValid) action(ds) } func TestTestDataSource(t *testing.T) { t.Run("initializes with empty data", func(t *testing.T) { testDataSourceTest(t, func(p testDataSourceTestParams) { p.withDataSource(t, func(ds subsystems.DataSource) { expectedData := ldservices.NewServerSDKData() p.updates.DataStore.WaitForInit(t, expectedData, time.Millisecond) assert.True(t, ds.IsInitialized()) }) }) }) t.Run("initializes with flags", func(t *testing.T) { testDataSourceTest(t, func(p testDataSourceTestParams) { p.td.Update(p.td.Flag("flag1").On(true)). Update(p.td.Flag("flag2").On(false)) p.withDataSource(t, func(subsystems.DataSource) { initData := p.updates.DataStore.WaitForNextInit(t, time.Millisecond) dataMap := sharedtest.DataSetToMap(initData) require.Len(t, dataMap, 2) flags := dataMap[ldstoreimpl.Features()] require.Len(t, flags, 2) assert.Equal(t, 1, flags["flag1"].Version) assert.Equal(t, 1, flags["flag2"].Version) assert.True(t, flags["flag1"].Item.(*ldmodel.FeatureFlag).On) assert.False(t, flags["flag2"].Item.(*ldmodel.FeatureFlag).On) }) }) }) t.Run("adds flag", func(t *testing.T) { testDataSourceTest(t, func(p testDataSourceTestParams) { p.withDataSource(t, func(subsystems.DataSource) { p.td.Update(p.td.Flag("flag1").On(true)) up := p.updates.DataStore.WaitForUpsert(t, ldstoreimpl.Features(), "flag1", 1, time.Millisecond) assert.True(t, up.Item.Item.(*ldmodel.FeatureFlag).On) }) }) }) t.Run("updates flag", func(t *testing.T) { testDataSourceTest(t, func(p testDataSourceTestParams) { p.td.Update(p.td.Flag("flag1").On(false)) p.withDataSource(t, func(subsystems.DataSource) { p.td.Update(p.td.Flag("flag1").On(true)) up := p.updates.DataStore.WaitForUpsert(t, ldstoreimpl.Features(), "flag1", 2, time.Millisecond) assert.True(t, up.Item.Item.(*ldmodel.FeatureFlag).On) }) }) }) t.Run("updates status", func(t *testing.T) { testDataSourceTest(t, func(p testDataSourceTestParams) { p.withDataSource(t, func(subsystems.DataSource) { ei := interfaces.DataSourceErrorInfo{Kind: interfaces.DataSourceErrorKindNetworkError} p.td.UpdateStatus(interfaces.DataSourceStateInterrupted, ei) status := p.updates.RequireStatusOf(t, interfaces.DataSourceStateInterrupted) assert.Equal(t, ei, status.LastError) }) }) }) t.Run("adds or updates preconfigured flag", func(t *testing.T) { flagv1 := ldbuilders.NewFlagBuilder("flagkey").Version(1).On(true).TrackEvents(true).Build() testDataSourceTest(t, func(p testDataSourceTestParams) { p.withDataSource(t, func(subsystems.DataSource) { p.td.UsePreconfiguredFlag(flagv1) up := p.updates.DataStore.WaitForUpsert(t, ldstoreimpl.Features(), flagv1.Key, 1, time.Millisecond) assert.Equal(t, &flagv1, up.Item.Item.(*ldmodel.FeatureFlag)) updatedFlag := flagv1 updatedFlag.On = false expectedFlagV2 := updatedFlag expectedFlagV2.Version = 2 p.td.UsePreconfiguredFlag(updatedFlag) up = p.updates.DataStore.WaitForUpsert(t, ldstoreimpl.Features(), flagv1.Key, 2, time.Millisecond) assert.Equal(t, &expectedFlagV2, up.Item.Item.(*ldmodel.FeatureFlag)) }) }) }) t.Run("adds or updates preconfigured segment", func(t *testing.T) { segmentv1 := ldbuilders.NewSegmentBuilder("segmentkey").Version(1).Included("a").Build() testDataSourceTest(t, func(p testDataSourceTestParams) { p.withDataSource(t, func(subsystems.DataSource) { p.td.UsePreconfiguredSegment(segmentv1) up := p.updates.DataStore.WaitForUpsert(t, ldstoreimpl.Segments(), segmentv1.Key, 1, time.Millisecond) assert.Equal(t, &segmentv1, up.Item.Item.(*ldmodel.Segment)) updatedSegment := segmentv1 updatedSegment.Included = []string{"b"} expectedSegmentV2 := updatedSegment expectedSegmentV2.Version = 2 p.td.UsePreconfiguredSegment(updatedSegment) up = p.updates.DataStore.WaitForUpsert(t, ldstoreimpl.Segments(), segmentv1.Key, 2, time.Millisecond) assert.Equal(t, &expectedSegmentV2, up.Item.Item.(*ldmodel.Segment)) }) }) }) }