...

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

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

     1  package ldfiledata
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"testing"
     7  
     8  	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks"
     9  
    10  	"github.com/launchdarkly/go-sdk-common/v3/ldlog"
    11  	"github.com/launchdarkly/go-sdk-common/v3/ldlogtest"
    12  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
    13  	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
    14  	"github.com/launchdarkly/go-server-sdk/v6/interfaces"
    15  	"github.com/launchdarkly/go-server-sdk/v6/internal/datakinds"
    16  	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest"
    17  	"github.com/launchdarkly/go-server-sdk/v6/ldcomponents"
    18  	"github.com/launchdarkly/go-server-sdk/v6/subsystems"
    19  
    20  	th "github.com/launchdarkly/go-test-helpers/v3"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  type fileDataSourceTestParams struct {
    27  	dataSource     subsystems.DataSource
    28  	updates        *mocks.MockDataSourceUpdates
    29  	mockLog        *ldlogtest.MockLog
    30  	closeWhenReady chan struct{}
    31  }
    32  
    33  func (p fileDataSourceTestParams) waitForStart() {
    34  	p.dataSource.Start(p.closeWhenReady)
    35  	<-p.closeWhenReady
    36  }
    37  
    38  func withFileDataSourceTestParams(
    39  	factory subsystems.ComponentConfigurer[subsystems.DataSource],
    40  	action func(fileDataSourceTestParams),
    41  ) {
    42  	p := fileDataSourceTestParams{}
    43  	mockLog := ldlogtest.NewMockLog()
    44  	testContext := sharedtest.NewTestContext("", nil, &subsystems.LoggingConfiguration{Loggers: mockLog.Loggers})
    45  	store, _ := ldcomponents.InMemoryDataStore().Build(testContext)
    46  	updates := mocks.NewMockDataSourceUpdates(store)
    47  	testContext.DataSourceUpdateSink = updates
    48  	dataSource, err := factory.Build(testContext)
    49  	if err != nil {
    50  		panic(err)
    51  	}
    52  	defer dataSource.Close()
    53  	p.dataSource = dataSource
    54  	action(fileDataSourceTestParams{dataSource, updates, mockLog, make(chan struct{})})
    55  }
    56  
    57  func expectCreationError(t *testing.T, factory subsystems.ComponentConfigurer[subsystems.DataSource]) error {
    58  	testContext := sharedtest.NewTestContext("", nil, nil)
    59  	store, _ := ldcomponents.InMemoryDataStore().Build(testContext)
    60  	updates := mocks.NewMockDataSourceUpdates(store)
    61  	testContext.DataSourceUpdateSink = updates
    62  	dataSource, err := factory.Build(testContext)
    63  	require.Error(t, err)
    64  	require.Nil(t, dataSource)
    65  	return err
    66  }
    67  
    68  func TestNewFileDataSourceYaml(t *testing.T) {
    69  	fileData := `
    70  ---
    71  flags:
    72    my-flag:
    73      "on": true
    74  segments:
    75    my-segment:
    76      rules: []
    77  `
    78  	th.WithTempFileData([]byte(fileData), func(filename string) {
    79  		factory := DataSource().FilePaths(filename)
    80  		withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
    81  			p.waitForStart()
    82  			require.True(t, p.dataSource.IsInitialized())
    83  
    84  			flag := requireFlag(t, p.updates.DataStore, "my-flag")
    85  			assert.True(t, flag.On)
    86  
    87  			segment := requireSegment(t, p.updates.DataStore, "my-segment")
    88  			assert.Empty(t, segment.Rules)
    89  		})
    90  	})
    91  }
    92  
    93  func TestNewFileDataSourceJson(t *testing.T) {
    94  	th.WithTempFileData([]byte(`{"flags": {"my-flag": {"on": true}}}`), func(filename string) {
    95  		factory := DataSource().FilePaths(filename)
    96  		withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
    97  			p.waitForStart()
    98  			require.True(t, p.dataSource.IsInitialized())
    99  
   100  			flag := requireFlag(t, p.updates.DataStore, "my-flag")
   101  			assert.True(t, flag.On)
   102  		})
   103  	})
   104  }
   105  
   106  func TestStatusIsValidAfterSuccessfulLoad(t *testing.T) {
   107  	th.WithTempFileData([]byte(`{"flags": {"my-flag": {"on": true}}}`), func(filename string) {
   108  		factory := DataSource().FilePaths(filename)
   109  		withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
   110  			p.waitForStart()
   111  			require.True(t, p.dataSource.IsInitialized())
   112  
   113  			p.updates.RequireStatusOf(t, interfaces.DataSourceStateValid)
   114  		})
   115  	})
   116  }
   117  
   118  func TestNewFileDataSourceJsonWithTwoFiles(t *testing.T) {
   119  	th.WithTempFileData([]byte(`{"flags": {"my-flag1": {"on": true}}}`), func(filename1 string) {
   120  		th.WithTempFileData([]byte(`{"flags": {"my-flag2": {"on": true}}}`), func(filename2 string) {
   121  			factory := DataSource().FilePaths(filename1, filename2)
   122  			withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
   123  				p.waitForStart()
   124  				require.True(t, p.dataSource.IsInitialized())
   125  
   126  				flag1 := requireFlag(t, p.updates.DataStore, "my-flag1")
   127  				assert.True(t, flag1.On)
   128  
   129  				flag2 := requireFlag(t, p.updates.DataStore, "my-flag2")
   130  				assert.True(t, flag2.On)
   131  			})
   132  		})
   133  	})
   134  }
   135  
   136  func TestNewFileDataSourceJsonWithTwoConflictingFiles(t *testing.T) {
   137  	file1Data := `{"flags": {"flag1": {"on": true}, "flag2": {"on": true}}, "segments": {"segment1": {}}}`
   138  	file2Data := `{"flags": {"flag2": {"on": true}}}`
   139  	file3Data := `{"flagValues": {"flag2": true}}`
   140  	file4Data := `{"segments": {"segment1": {}}}`
   141  
   142  	th.WithTempFileData([]byte(file1Data), func(filename1 string) {
   143  		for _, data := range []string{file2Data, file3Data, file4Data} {
   144  			th.WithTempFileData([]byte(data), func(filename2 string) {
   145  				factory := DataSource().FilePaths(filename1, filename2)
   146  				withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
   147  					p.waitForStart()
   148  					require.False(t, p.dataSource.IsInitialized())
   149  
   150  					p.mockLog.AssertMessageMatch(t, true, ldlog.Error, "specified by multiple files")
   151  				})
   152  			})
   153  		}
   154  	})
   155  }
   156  
   157  func TestDuplicateKeysHandlingCanSuppressErrors(t *testing.T) {
   158  	file1Data := `{"flags": {"flag1": {"on": true}, "flag2": {"on": false}}, "segments": {"segment1": {}}}`
   159  	file2Data := `{"flags": {"flag2": {"on": true}}}`
   160  
   161  	th.WithTempFileData([]byte(file1Data), func(filename1 string) {
   162  		th.WithTempFileData([]byte(file2Data), func(filename2 string) {
   163  			factory := DataSource().FilePaths(filename1, filename2).
   164  				DuplicateKeysHandling(DuplicateKeysIgnoreAllButFirst)
   165  			withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
   166  				p.waitForStart()
   167  				require.True(t, p.dataSource.IsInitialized())
   168  
   169  				flag2 := requireFlag(t, p.updates.DataStore, "flag2")
   170  				assert.False(t, flag2.On)
   171  
   172  				p.mockLog.AssertMessageMatch(t, false, ldlog.Error, "specified by multiple files")
   173  			})
   174  		})
   175  	})
   176  }
   177  
   178  func TestNewFileDataSourceBadData(t *testing.T) {
   179  	th.WithTempFileData([]byte(`bad data`), func(filename string) {
   180  		factory := DataSource().FilePaths(filename)
   181  		withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
   182  			p.waitForStart()
   183  			require.False(t, p.dataSource.IsInitialized())
   184  		})
   185  	})
   186  }
   187  
   188  func TestNewFileDataSourceMissingFile(t *testing.T) {
   189  	th.WithTempFileData([]byte{}, func(filename string) {
   190  		os.Remove(filename)
   191  
   192  		factory := DataSource().FilePaths(filename)
   193  		withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
   194  			p.waitForStart()
   195  			assert.False(t, p.dataSource.IsInitialized())
   196  		})
   197  	})
   198  }
   199  
   200  func TestStatusIsInterruptedAfterUnsuccessfulLoad(t *testing.T) {
   201  	th.WithTempFileData([]byte(`bad data`), func(filename string) {
   202  		factory := DataSource().FilePaths(filename)
   203  		withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
   204  			p.waitForStart()
   205  			require.False(t, p.dataSource.IsInitialized())
   206  
   207  			p.updates.RequireStatusOf(t, interfaces.DataSourceStateInterrupted)
   208  		})
   209  	})
   210  }
   211  
   212  func TestNewFileDataSourceYamlValues(t *testing.T) {
   213  	fileData := `
   214  ---
   215  flagValues:
   216    my-flag: true
   217  `
   218  	th.WithTempFileData([]byte(fileData), func(filename string) {
   219  		factory := DataSource().FilePaths(filename)
   220  		withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
   221  			p.waitForStart()
   222  			require.True(t, p.dataSource.IsInitialized())
   223  
   224  			flag := requireFlag(t, p.updates.DataStore, "my-flag")
   225  			assert.Equal(t, []ldvalue.Value{ldvalue.Bool(true)}, flag.Variations)
   226  		})
   227  	})
   228  }
   229  
   230  func TestReloaderFailureDoesNotPreventStarting(t *testing.T) {
   231  	e := errors.New("sorry")
   232  	f := func(paths []string, loggers ldlog.Loggers, reload func(), closeCh <-chan struct{}) error {
   233  		return e
   234  	}
   235  	factory := DataSource().Reloader(f)
   236  	withFileDataSourceTestParams(factory, func(p fileDataSourceTestParams) {
   237  		p.waitForStart()
   238  		assert.True(t, p.dataSource.IsInitialized())
   239  		assert.Len(t, p.mockLog.GetOutput(ldlog.Error), 1)
   240  	})
   241  }
   242  
   243  func requireFlag(t *testing.T, store subsystems.DataStore, key string) *ldmodel.FeatureFlag {
   244  	item, err := store.Get(datakinds.Features, key)
   245  	require.NoError(t, err)
   246  	require.NotNil(t, item.Item)
   247  	return item.Item.(*ldmodel.FeatureFlag)
   248  }
   249  
   250  func requireSegment(t *testing.T, store subsystems.DataStore, key string) *ldmodel.Segment {
   251  	item, err := store.Get(datakinds.Segments, key)
   252  	require.NoError(t, err)
   253  	require.NotNil(t, item.Item)
   254  	return item.Item.(*ldmodel.Segment)
   255  }
   256  

View as plain text