1 package evaluation
2
3 import (
4 "fmt"
5 "testing"
6
7 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
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/ldvalue"
13 m "github.com/launchdarkly/go-test-helpers/v3/matchers"
14
15 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"
17 )
18
19 type prereqEventSink struct {
20 events []PrerequisiteFlagEvent
21 }
22
23 func (p *prereqEventSink) record(event PrerequisiteFlagEvent) {
24 p.events = append(p.events, event)
25 }
26
27 func TestFlagReturnsOffVariationIfPrerequisiteIsNotFound(t *testing.T) {
28 f0 := ldbuilders.NewFlagBuilder("feature0").
29 On(true).
30 OffVariation(1).
31 AddPrerequisite("feature1", 1).
32 FallthroughVariation(0).
33 Variations(fallthroughValue, offValue, onValue).
34 Build()
35 evaluator := NewEvaluator(basicDataProvider().withNonexistentFlag("feature1"))
36
37 eventSink := prereqEventSink{}
38 result := evaluator.Evaluate(&f0, flagTestContext, eventSink.record)
39 m.In(t).Assert(result, ResultDetailProps(1, offValue, ldreason.NewEvalReasonPrerequisiteFailed("feature1")))
40 assert.Equal(t, 0, len(eventSink.events))
41 }
42
43 func TestFlagReturnsOffVariationAndEventIfPrerequisiteIsOff(t *testing.T) {
44 f0 := ldbuilders.NewFlagBuilder("feature0").
45 On(true).
46 OffVariation(1).
47 AddPrerequisite("feature1", 1).
48 FallthroughVariation(0).
49 Variations(fallthroughValue, offValue, onValue).
50 Build()
51 f1 := ldbuilders.NewFlagBuilder("feature1").
52 On(false).
53 OffVariation(1).
54
55 FallthroughVariation(0).
56 Variations(ldvalue.String("nogo"), ldvalue.String("go")).
57 Build()
58 evaluator := NewEvaluator(basicDataProvider().withStoredFlags(f1))
59
60 eventSink := prereqEventSink{}
61 result := evaluator.Evaluate(&f0, flagTestContext, eventSink.record)
62 m.In(t).Assert(result, ResultDetailProps(1, offValue, ldreason.NewEvalReasonPrerequisiteFailed("feature1")))
63
64 assert.Equal(t, 1, len(eventSink.events))
65 e := eventSink.events[0]
66 assert.Equal(t, f0.Key, e.TargetFlagKey)
67 assert.Equal(t, flagTestContext, e.Context)
68 assert.Equal(t, &f1, e.PrerequisiteFlag)
69 m.In(t).Assert(e.PrerequisiteResult, ResultDetailProps(1, ldvalue.String("go"), ldreason.NewEvalReasonOff()))
70 }
71
72 func TestFlagReturnsOffVariationAndEventIfPrerequisiteIsNotMet(t *testing.T) {
73 f0 := ldbuilders.NewFlagBuilder("feature0").
74 On(true).
75 OffVariation(1).
76 AddPrerequisite("feature1", 1).
77 FallthroughVariation(0).
78 Variations(fallthroughValue, offValue, onValue).
79 Build()
80 f1 := ldbuilders.NewFlagBuilder("feature1").
81 On(true).
82 FallthroughVariation(0).
83 Variations(ldvalue.String("nogo"), ldvalue.String("go")).
84 Build()
85 evaluator := NewEvaluator(basicDataProvider().withStoredFlags(f1))
86
87 eventSink := prereqEventSink{}
88 result := evaluator.Evaluate(&f0, flagTestContext, eventSink.record)
89 m.In(t).Assert(result, ResultDetailProps(1, offValue, ldreason.NewEvalReasonPrerequisiteFailed("feature1")))
90
91 assert.Equal(t, 1, len(eventSink.events))
92 e := eventSink.events[0]
93 assert.Equal(t, f0.Key, e.TargetFlagKey)
94 assert.Equal(t, flagTestContext, e.Context)
95 assert.Equal(t, &f1, e.PrerequisiteFlag)
96 m.In(t).Assert(e.PrerequisiteResult, ResultDetailProps(0, ldvalue.String("nogo"), ldreason.NewEvalReasonFallthrough()))
97 }
98
99 func TestFlagReturnsFallthroughVariationAndEventIfPrerequisiteIsMetAndThereAreNoRules(t *testing.T) {
100 f0 := ldbuilders.NewFlagBuilder("feature0").
101 On(true).
102 OffVariation(1).
103 AddPrerequisite("feature1", 1).
104 FallthroughVariation(0).
105 Variations(fallthroughValue, offValue, onValue).
106 Build()
107 f1 := ldbuilders.NewFlagBuilder("feature1").
108 On(true).
109 FallthroughVariation(1).
110 Variations(ldvalue.String("nogo"), ldvalue.String("go")).
111 Build()
112 evaluator := NewEvaluator(basicDataProvider().withStoredFlags(f1))
113
114 eventSink := prereqEventSink{}
115 result := evaluator.Evaluate(&f0, flagTestContext, eventSink.record)
116 m.In(t).Assert(result, ResultDetailProps(0, fallthroughValue, ldreason.NewEvalReasonFallthrough()))
117
118 assert.Equal(t, 1, len(eventSink.events))
119 e := eventSink.events[0]
120 assert.Equal(t, f0.Key, e.TargetFlagKey)
121 assert.Equal(t, flagTestContext, e.Context)
122 assert.Equal(t, &f1, e.PrerequisiteFlag)
123 m.In(t).Assert(e.PrerequisiteResult, ResultDetailProps(1, ldvalue.String("go"), ldreason.NewEvalReasonFallthrough()))
124 }
125
126 func TestPrerequisiteCanMatchWithNonScalarValue(t *testing.T) {
127 f0 := ldbuilders.NewFlagBuilder("feature0").
128 On(true).
129 OffVariation(1).
130 AddPrerequisite("feature1", 1).
131 FallthroughVariation(0).
132 Variations(fallthroughValue, offValue, onValue).
133 Build()
134 prereqVar0 := ldvalue.ArrayOf(ldvalue.String("000"))
135 prereqVar1 := ldvalue.ArrayOf(ldvalue.String("001"))
136 f1 := ldbuilders.NewFlagBuilder("feature1").
137 On(true).
138 FallthroughVariation(1).
139 Variations(prereqVar0, prereqVar1).
140 Build()
141 evaluator := NewEvaluator(basicDataProvider().withStoredFlags(f1))
142
143 eventSink := prereqEventSink{}
144 result := evaluator.Evaluate(&f0, flagTestContext, eventSink.record)
145 m.In(t).Assert(result, ResultDetailProps(0, fallthroughValue, ldreason.NewEvalReasonFallthrough()))
146
147 assert.Equal(t, 1, len(eventSink.events))
148 e := eventSink.events[0]
149 assert.Equal(t, f0.Key, e.TargetFlagKey)
150 assert.Equal(t, flagTestContext, e.Context)
151 assert.Equal(t, &f1, e.PrerequisiteFlag)
152 m.In(t).Assert(e.PrerequisiteResult, ResultDetailProps(1, prereqVar1, ldreason.NewEvalReasonFallthrough()))
153 }
154
155 func TestMultipleLevelsOfPrerequisiteProduceMultipleEvents(t *testing.T) {
156 f0 := ldbuilders.NewFlagBuilder("feature0").
157 On(true).
158 OffVariation(1).
159 AddPrerequisite("feature1", 1).
160 FallthroughVariation(0).
161 Variations(fallthroughValue, offValue, onValue).
162 Build()
163 f1 := ldbuilders.NewFlagBuilder("feature1").
164 On(true).
165 AddPrerequisite("feature2", 1).
166 FallthroughVariation(1).
167 Variations(ldvalue.String("nogo"), ldvalue.String("go")).
168 Build()
169 f2 := ldbuilders.NewFlagBuilder("feature2").
170 On(true).
171 FallthroughVariation(1).
172 Variations(ldvalue.String("nogo"), ldvalue.String("go")).
173 Build()
174 evaluator := NewEvaluator(basicDataProvider().withStoredFlags(f1, f2))
175
176 eventSink := prereqEventSink{}
177 result := evaluator.Evaluate(&f0, flagTestContext, eventSink.record)
178 m.In(t).Assert(result, ResultDetailProps(0, fallthroughValue, ldreason.NewEvalReasonFallthrough()))
179
180 assert.Equal(t, 2, len(eventSink.events))
181
182
183 e0 := eventSink.events[0]
184 assert.Equal(t, f1.Key, e0.TargetFlagKey)
185 assert.Equal(t, flagTestContext, e0.Context)
186 assert.Equal(t, &f2, e0.PrerequisiteFlag)
187 m.In(t).Assert(e0.PrerequisiteResult, ResultDetailProps(1, ldvalue.String("go"), ldreason.NewEvalReasonFallthrough()))
188
189 e1 := eventSink.events[1]
190 assert.Equal(t, f0.Key, e1.TargetFlagKey)
191 assert.Equal(t, flagTestContext, e1.Context)
192 assert.Equal(t, &f1, e1.PrerequisiteFlag)
193 m.In(t).Assert(e1.PrerequisiteResult, ResultDetailProps(1, ldvalue.String("go"), ldreason.NewEvalReasonFallthrough()))
194 }
195
196 func TestPrerequisiteCycleDetection(t *testing.T) {
197 for _, cycleGoesToOriginalFlag := range []bool{true, false} {
198 t.Run(fmt.Sprintf("cycleGoesToOriginalFlag=%t", cycleGoesToOriginalFlag), func(t *testing.T) {
199 f0 := ldbuilders.NewFlagBuilder("feature0").
200 On(true).
201 OffVariation(1).
202 AddPrerequisite("feature1", 1).
203 FallthroughVariation(0).
204 Variations(fallthroughValue, offValue, onValue).
205 Build()
206 f1 := ldbuilders.NewFlagBuilder("feature1").
207 On(true).
208 AddPrerequisite("feature2", 1).
209 FallthroughVariation(1).
210 Variations(ldvalue.String("nogo"), ldvalue.String("go")).
211 Build()
212 cycleTargetKey := f1.Key
213 if cycleGoesToOriginalFlag {
214 cycleTargetKey = f0.Key
215 }
216 f2 := ldbuilders.NewFlagBuilder("feature2").
217 On(true).
218 AddPrerequisite(cycleTargetKey, 1).
219 FallthroughVariation(1).
220 Variations(ldvalue.String("nogo"), ldvalue.String("go")).
221 Build()
222
223 logCapture := ldlogtest.NewMockLog()
224 evaluator := NewEvaluatorWithOptions(
225 basicDataProvider().withStoredFlags(f0, f1, f2),
226 EvaluatorOptionErrorLogger(logCapture.Loggers.ForLevel(ldlog.Error)),
227 )
228
229 result := evaluator.Evaluate(&f0, flagTestContext, FailOnAnyPrereqEvent(t))
230 m.In(t).Assert(result, ResultDetailError(ldreason.EvalErrorMalformedFlag))
231
232
233
234
235
236 errorLines := logCapture.GetOutput(ldlog.Error)
237 require.Len(t, errorLines, 1)
238 assert.Regexp(t, `Invalid flag configuration.*prerequisite relationship.*circular reference`, errorLines[0])
239 })
240 }
241 }
242
View as plain text