...

Source file src/github.com/launchdarkly/go-server-sdk/v6/internal/datasource/data_model_dependencies_test.go

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

     1  package datasource
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks"
     8  
     9  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
    10  	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
    11  	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
    12  	"github.com/launchdarkly/go-server-sdk/v6/internal/datakinds"
    13  	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest"
    14  	st "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes"
    15  
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  func TestComputeDependenciesFromFlag(t *testing.T) {
    20  	flag1 := ldbuilders.NewFlagBuilder("key").Build()
    21  	assert.Len(
    22  		t,
    23  		computeDependenciesFrom(datakinds.Features, sharedtest.FlagDescriptor(flag1)),
    24  		0,
    25  	)
    26  
    27  	flag2 := ldbuilders.NewFlagBuilder("key").
    28  		AddPrerequisite("flag2", 0).
    29  		AddPrerequisite("flag3", 0).
    30  		AddRule(
    31  			ldbuilders.NewRuleBuilder().Clauses(
    32  				ldbuilders.Clause("key", ldmodel.OperatorIn, ldvalue.String("ignore")),
    33  				ldbuilders.SegmentMatchClause("segment1", "segment2"),
    34  			),
    35  		).
    36  		AddRule(
    37  			ldbuilders.NewRuleBuilder().Clauses(
    38  				ldbuilders.SegmentMatchClause("segment3"),
    39  			),
    40  		).
    41  		Build()
    42  	assert.Equal(
    43  		t,
    44  		kindAndKeySet{
    45  			{datakinds.Features, "flag2"}:    true,
    46  			{datakinds.Features, "flag3"}:    true,
    47  			{datakinds.Segments, "segment1"}: true,
    48  			{datakinds.Segments, "segment2"}: true,
    49  			{datakinds.Segments, "segment3"}: true,
    50  		},
    51  		computeDependenciesFrom(datakinds.Features, sharedtest.FlagDescriptor(flag2)),
    52  	)
    53  
    54  	flag3 := ldbuilders.NewFlagBuilder("key").
    55  		AddRule(
    56  			ldbuilders.NewRuleBuilder().Clauses(
    57  				ldbuilders.Clause("key}", ldmodel.OperatorIn, ldvalue.String("ignore")),
    58  				ldbuilders.SegmentMatchClause("segment1", "segment2"),
    59  			),
    60  		).
    61  		Build()
    62  	assert.Equal(
    63  		t,
    64  		kindAndKeySet{
    65  			{datakinds.Segments, "segment1"}: true,
    66  			{datakinds.Segments, "segment2"}: true,
    67  		},
    68  		computeDependenciesFrom(datakinds.Features, sharedtest.FlagDescriptor(flag3)),
    69  	)
    70  }
    71  
    72  func TestComputeDependenciesFromSegment(t *testing.T) {
    73  	segment := ldbuilders.NewSegmentBuilder("segment").Build()
    74  	assert.Len(
    75  		t,
    76  		computeDependenciesFrom(datakinds.Segments, st.ItemDescriptor{Version: segment.Version, Item: &segment}),
    77  		0,
    78  	)
    79  }
    80  
    81  func TestComputeDependenciesFromSegmentWithSegmentReferences(t *testing.T) {
    82  	segment1 := ldbuilders.NewSegmentBuilder("segment1").
    83  		AddRule(ldbuilders.NewSegmentRuleBuilder().Clauses(
    84  			ldbuilders.SegmentMatchClause("segment2", "segment3"),
    85  		)).
    86  		Build()
    87  	assert.Equal(
    88  		t,
    89  		kindAndKeySet{
    90  			{datakinds.Segments, "segment2"}: true,
    91  			{datakinds.Segments, "segment3"}: true,
    92  		},
    93  		computeDependenciesFrom(datakinds.Segments, st.ItemDescriptor{Version: segment1.Version, Item: &segment1}),
    94  	)
    95  }
    96  
    97  func TestComputeDependenciesFromUnknownDataKind(t *testing.T) {
    98  	assert.Len(
    99  		t,
   100  		computeDependenciesFrom(mocks.MockData, st.ItemDescriptor{Version: 1, Item: "x"}),
   101  		0,
   102  	)
   103  }
   104  
   105  func TestComputeDependenciesFromNullItem(t *testing.T) {
   106  	assert.Len(
   107  		t,
   108  		computeDependenciesFrom(datakinds.Features, st.ItemDescriptor{Version: 1, Item: nil}),
   109  		0,
   110  	)
   111  }
   112  
   113  func TestSortCollectionsForDataStoreInit(t *testing.T) {
   114  	inputData := makeDependencyOrderingDataSourceTestData()
   115  	sortedData := sortCollectionsForDataStoreInit(inputData)
   116  	verifySortedData(t, sortedData, inputData)
   117  }
   118  
   119  func TestSortCollectionsLeavesItemsOfUnknownDataKindUnchanged(t *testing.T) {
   120  	item1 := mocks.MockDataItem{Key: "item1"}
   121  	item2 := mocks.MockDataItem{Key: "item2"}
   122  	flag := ldbuilders.NewFlagBuilder("a").Build()
   123  	inputData := []st.Collection{
   124  		{Kind: mocks.MockData,
   125  			Items: []st.KeyedItemDescriptor{
   126  				{Key: item1.Key, Item: item1.ToItemDescriptor()},
   127  				{Key: item2.Key, Item: item2.ToItemDescriptor()},
   128  			}},
   129  		{Kind: datakinds.Features,
   130  			Items: []st.KeyedItemDescriptor{
   131  				{Key: "a", Item: sharedtest.FlagDescriptor(flag)},
   132  			}},
   133  		{Kind: datakinds.Segments, Items: nil},
   134  	}
   135  	sortedData := sortCollectionsForDataStoreInit(inputData)
   136  
   137  	// the unknown data kind appears last, and the ordering of its items is unchanged
   138  	assert.Len(t, sortedData, 3)
   139  	assert.Equal(t, datakinds.Segments, sortedData[0].Kind)
   140  	assert.Equal(t, datakinds.Features, sortedData[1].Kind)
   141  	assert.Equal(t, mocks.MockData, sortedData[2].Kind)
   142  	assert.Equal(t, inputData[0].Items, sortedData[2].Items)
   143  }
   144  
   145  func TestDependencyTrackerReturnsSingleValueResultForUnknownItem(t *testing.T) {
   146  	dt := newDependencyTracker()
   147  
   148  	// a change to any item with no known depenencies affects only itself
   149  	verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag1", kindAndKey{datakinds.Features, "flag1"})
   150  }
   151  
   152  func TestDependencyTrackerBuildsGraph(t *testing.T) {
   153  	dt := newDependencyTracker()
   154  
   155  	segment3 := ldbuilders.NewSegmentBuilder("segment3").Build()
   156  	segment2 := ldbuilders.NewSegmentBuilder("segment2").
   157  		AddRule(ldbuilders.NewSegmentRuleBuilder().Clauses(
   158  			ldbuilders.SegmentMatchClause(segment3.Key),
   159  		)).
   160  		Build()
   161  	segment1 := ldbuilders.NewSegmentBuilder("segment1").Build()
   162  
   163  	flag1 := ldbuilders.NewFlagBuilder("flag1").
   164  		AddPrerequisite("flag2", 0).
   165  		AddPrerequisite("flag3", 0).
   166  		AddRule(
   167  			ldbuilders.NewRuleBuilder().Clauses(
   168  				ldbuilders.SegmentMatchClause(segment1.Key, segment2.Key),
   169  			),
   170  		).
   171  		Build()
   172  
   173  	flag2 := ldbuilders.NewFlagBuilder("flag2").
   174  		AddPrerequisite("flag4", 0).
   175  		AddRule(
   176  			ldbuilders.NewRuleBuilder().Clauses(
   177  				ldbuilders.SegmentMatchClause(segment2.Key),
   178  			),
   179  		).
   180  		Build()
   181  
   182  	for _, s := range []ldmodel.Segment{segment1, segment2, segment3} {
   183  		dt.updateDependenciesFrom(datakinds.Segments, s.Key, sharedtest.SegmentDescriptor(s))
   184  	}
   185  	for _, f := range []ldmodel.FeatureFlag{flag1, flag2} {
   186  		dt.updateDependenciesFrom(datakinds.Features, f.Key, sharedtest.FlagDescriptor(f))
   187  	}
   188  
   189  	// a change to flag1 affects only flag1
   190  	verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag1",
   191  		kindAndKey{datakinds.Features, "flag1"},
   192  	)
   193  
   194  	// a change to flag2 affects flag2 and flag1
   195  	verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag2",
   196  		kindAndKey{datakinds.Features, "flag2"},
   197  		kindAndKey{datakinds.Features, "flag1"},
   198  	)
   199  
   200  	// a change to flag3 affects flag3 and flag1
   201  	verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag3",
   202  		kindAndKey{datakinds.Features, "flag3"},
   203  		kindAndKey{datakinds.Features, "flag1"},
   204  	)
   205  
   206  	// a change to segment1 affects segment1 and flag1
   207  	verifyDependencyAffectedItems(t, dt, datakinds.Segments, "segment1",
   208  		kindAndKey{datakinds.Segments, "segment1"},
   209  		kindAndKey{datakinds.Features, "flag1"},
   210  	)
   211  
   212  	// a change to segment2 affects segment2, flag1, and flag2
   213  	verifyDependencyAffectedItems(t, dt, datakinds.Segments, "segment2",
   214  		kindAndKey{datakinds.Segments, "segment2"},
   215  		kindAndKey{datakinds.Features, "flag1"},
   216  		kindAndKey{datakinds.Features, "flag2"},
   217  	)
   218  
   219  	// a change to segment3 affects segment2, which affects flag1 and flag2
   220  	verifyDependencyAffectedItems(t, dt, datakinds.Segments, "segment3",
   221  		kindAndKey{datakinds.Segments, "segment3"},
   222  		kindAndKey{datakinds.Segments, "segment2"},
   223  		kindAndKey{datakinds.Features, "flag1"},
   224  		kindAndKey{datakinds.Features, "flag2"},
   225  	)
   226  }
   227  
   228  func TestDependencyTrackerUpdatesGraph(t *testing.T) {
   229  	dt := newDependencyTracker()
   230  
   231  	flag1 := ldbuilders.NewFlagBuilder("flag1").
   232  		AddPrerequisite("flag3", 0).
   233  		Build()
   234  	dt.updateDependenciesFrom(datakinds.Features, flag1.Key, st.ItemDescriptor{Version: flag1.Version, Item: &flag1})
   235  
   236  	flag2 := ldbuilders.NewFlagBuilder("flag2").
   237  		AddPrerequisite("flag3", 0).
   238  		Build()
   239  	dt.updateDependenciesFrom(datakinds.Features, flag2.Key, st.ItemDescriptor{Version: flag2.Version, Item: &flag2})
   240  
   241  	// at this point, a change to flag3 affects flag3, flag2, and flag1
   242  	verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag3",
   243  		kindAndKey{datakinds.Features, "flag3"},
   244  		kindAndKey{datakinds.Features, "flag2"},
   245  		kindAndKey{datakinds.Features, "flag1"},
   246  	)
   247  
   248  	// now make it so flag1 now depends on flag4 instead of flag2
   249  	flag1v2 := ldbuilders.NewFlagBuilder("flag1").
   250  		AddPrerequisite("flag4", 0).
   251  		Build()
   252  	dt.updateDependenciesFrom(datakinds.Features, flag1.Key, st.ItemDescriptor{Version: flag1v2.Version, Item: &flag1v2})
   253  
   254  	// now, a change to flag3 affects flag3 and flag2
   255  	verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag3",
   256  		kindAndKey{datakinds.Features, "flag3"},
   257  		kindAndKey{datakinds.Features, "flag2"},
   258  	)
   259  
   260  	// and a change to flag4 affects flag4 and flag1
   261  	verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag4",
   262  		kindAndKey{datakinds.Features, "flag4"},
   263  		kindAndKey{datakinds.Features, "flag1"},
   264  	)
   265  }
   266  
   267  func TestDependencyTrackerResetsGraph(t *testing.T) {
   268  	dt := newDependencyTracker()
   269  
   270  	flag1 := ldbuilders.NewFlagBuilder("flag1").
   271  		AddPrerequisite("flag3", 0).
   272  		Build()
   273  	dt.updateDependenciesFrom(datakinds.Features, flag1.Key, st.ItemDescriptor{Version: flag1.Version, Item: &flag1})
   274  
   275  	verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag3",
   276  		kindAndKey{datakinds.Features, "flag3"},
   277  		kindAndKey{datakinds.Features, "flag1"},
   278  	)
   279  
   280  	dt.reset()
   281  
   282  	verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag3",
   283  		kindAndKey{datakinds.Features, "flag3"},
   284  	)
   285  }
   286  
   287  func verifyDependencyAffectedItems(
   288  	t *testing.T,
   289  	dt *dependencyTracker,
   290  	kind st.DataKind,
   291  	key string,
   292  	expected ...kindAndKey,
   293  ) {
   294  	expectedSet := make(kindAndKeySet)
   295  	for _, value := range expected {
   296  		expectedSet.add(value)
   297  	}
   298  	result := make(kindAndKeySet)
   299  	dt.addAffectedItems(result, kindAndKey{kind, key})
   300  	assert.Equal(t, expectedSet, result)
   301  }
   302  
   303  func makeDependencyOrderingDataSourceTestData() []st.Collection {
   304  	return sharedtest.NewDataSetBuilder().
   305  		Flags(
   306  			ldbuilders.NewFlagBuilder("a").AddPrerequisite("b", 0).AddPrerequisite("c", 0).Build(),
   307  			ldbuilders.NewFlagBuilder("b").AddPrerequisite("c", 0).AddPrerequisite("e", 0).Build(),
   308  			ldbuilders.NewFlagBuilder("c").Build(),
   309  			ldbuilders.NewFlagBuilder("d").Build(),
   310  			ldbuilders.NewFlagBuilder("e").Build(),
   311  			ldbuilders.NewFlagBuilder("f").Build(),
   312  		).
   313  		Segments(
   314  			ldbuilders.NewSegmentBuilder("1").Build(),
   315  		).
   316  		Build()
   317  }
   318  
   319  func verifySortedData(t *testing.T, sortedData []st.Collection, inputData []st.Collection) {
   320  	assert.Len(t, sortedData, len(inputData))
   321  
   322  	assert.Equal(t, datakinds.Segments, sortedData[0].Kind) // Segments should always be first
   323  	assert.Equal(t, datakinds.Features, sortedData[1].Kind)
   324  
   325  	inputDataMap := fullDataSetToMap(inputData)
   326  	assert.Len(t, sortedData[0].Items, len(inputDataMap[datakinds.Segments]))
   327  	assert.Len(t, sortedData[1].Items, len(inputDataMap[datakinds.Features]))
   328  
   329  	flags := sortedData[1].Items
   330  	findFlagIndex := func(key string) int {
   331  		for i, item := range flags {
   332  			if item.Key == key {
   333  				return i
   334  			}
   335  		}
   336  		return -1
   337  	}
   338  
   339  	for _, item := range inputData[0].Items {
   340  		if flag, ok := item.Item.Item.(*ldmodel.FeatureFlag); ok {
   341  			flagIndex := findFlagIndex(item.Key)
   342  			for _, prereq := range flag.Prerequisites {
   343  				prereqIndex := findFlagIndex(prereq.Key)
   344  				if prereqIndex > flagIndex {
   345  					keys := make([]string, 0, len(flags))
   346  					for _, item := range flags {
   347  						keys = append(keys, item.Key)
   348  					}
   349  					assert.True(t, false, "%s depends on %s, but %s was listed first; keys in order are [%s]",
   350  						flag.Key, prereq.Key, strings.Join(keys, ", "))
   351  				}
   352  			}
   353  		}
   354  	}
   355  }
   356  

View as plain text