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
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
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
190 verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag1",
191 kindAndKey{datakinds.Features, "flag1"},
192 )
193
194
195 verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag2",
196 kindAndKey{datakinds.Features, "flag2"},
197 kindAndKey{datakinds.Features, "flag1"},
198 )
199
200
201 verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag3",
202 kindAndKey{datakinds.Features, "flag3"},
203 kindAndKey{datakinds.Features, "flag1"},
204 )
205
206
207 verifyDependencyAffectedItems(t, dt, datakinds.Segments, "segment1",
208 kindAndKey{datakinds.Segments, "segment1"},
209 kindAndKey{datakinds.Features, "flag1"},
210 )
211
212
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
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
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
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
255 verifyDependencyAffectedItems(t, dt, datakinds.Features, "flag3",
256 kindAndKey{datakinds.Features, "flag3"},
257 kindAndKey{datakinds.Features, "flag2"},
258 )
259
260
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)
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