1 package evaluation
2
3 import (
4 "fmt"
5 "testing"
6
7 "github.com/launchdarkly/go-sdk-common/v3/ldattr"
8 "github.com/launchdarkly/go-sdk-common/v3/ldcontext"
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
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15 )
16
17
18
19
20
21
22 func assertClauseMatch(t *testing.T, shouldMatch bool, clause ldmodel.Clause, context ldcontext.Context) {
23 match, err := makeEvalScope(context).clauseMatchesContext(&clause, evaluationStack{})
24 assert.NoError(t, err)
25 assert.Equal(t, shouldMatch, match)
26 }
27
28 type clauseMatchParams struct {
29 name string
30 clause ldmodel.Clause
31 context ldcontext.Context
32 shouldMatch bool
33 }
34
35 func doClauseMatchTest(t *testing.T, p clauseMatchParams) {
36 desc := "should not match"
37 if p.shouldMatch {
38 desc = "should match"
39 }
40 t.Run(fmt.Sprintf("%s, %s", p.name, desc), func(t *testing.T) {
41 match, err := makeEvalScope(p.context).clauseMatchesContext(&p.clause, evaluationStack{})
42 require.NoError(t, err)
43 assert.Equal(t, p.shouldMatch, match)
44 })
45 }
46
47 func TestClauseMatch(t *testing.T) {
48 allParams := []clauseMatchParams{
49
50 {
51 name: "built-in: key",
52 clause: ldbuilders.Clause(ldattr.KeyAttr, ldmodel.OperatorIn, ldvalue.String("a")),
53 context: ldcontext.New("a"),
54 shouldMatch: true,
55 },
56 {
57 name: "built-in: name",
58 clause: ldbuilders.Clause(ldattr.NameAttr, ldmodel.OperatorIn, ldvalue.String("b")),
59 context: ldcontext.NewBuilder("a").Name("b").Build(),
60 shouldMatch: true,
61 },
62 {
63 name: "built-in: anonymous",
64 clause: ldbuilders.Clause(ldattr.AnonymousAttr, ldmodel.OperatorIn, ldvalue.Bool(true)),
65 context: ldcontext.NewBuilder("a").Anonymous(true).Build(),
66 shouldMatch: true,
67 },
68 {
69 name: "custom",
70 clause: ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("b")),
71 context: ldcontext.NewBuilder("a").SetString("attr1", "b").Build(),
72 shouldMatch: true,
73 },
74 {
75 name: "single context value, multiple clause values",
76 clause: ldbuilders.Clause(ldattr.KeyAttr, ldmodel.OperatorIn, ldvalue.String("a"), ldvalue.String("b")),
77 context: ldcontext.New("b"),
78 shouldMatch: true,
79 },
80 {
81 name: "multiple context values, single clause value",
82 clause: ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("c")),
83 context: ldcontext.NewBuilder("a").SetValue("attr1",
84 ldvalue.ArrayOf(ldvalue.String("b"), ldvalue.String("c"))).Build(),
85 shouldMatch: true,
86 },
87 {
88 name: "multiple context values, multiple clause values",
89 clause: ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("c"), ldvalue.String("d")),
90 context: ldcontext.NewBuilder("a").SetValue("attr1",
91 ldvalue.ArrayOf(ldvalue.String("b"), ldvalue.String("c"))).Build(),
92 shouldMatch: true,
93 },
94 {
95 name: "single value non-match negated",
96 clause: ldbuilders.Negate(ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("b"))),
97 context: ldcontext.NewBuilder("a").SetString("attr1", "c").Build(),
98 shouldMatch: true,
99 },
100 {
101 name: "multi-value non-match negated",
102 clause: ldbuilders.Negate(ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("b"), ldvalue.String("c"))),
103 context: ldcontext.NewBuilder("a").SetString("attr1", "d").Build(),
104 shouldMatch: true,
105 },
106
107
108 {
109 name: "built-in: key",
110 clause: ldbuilders.Clause(ldattr.KeyAttr, ldmodel.OperatorIn, ldvalue.String("a")),
111 context: ldcontext.New("b"),
112 shouldMatch: false,
113 },
114 {
115 name: "built-in: name",
116 clause: ldbuilders.Clause(ldattr.NameAttr, ldmodel.OperatorIn, ldvalue.String("b")),
117 context: ldcontext.NewBuilder("a").Name("c").Build(),
118 shouldMatch: false,
119 },
120 {
121 name: "built-in: anonymous",
122 clause: ldbuilders.Clause(ldattr.AnonymousAttr, ldmodel.OperatorIn, ldvalue.Bool(true)),
123 context: ldcontext.NewBuilder("a").Anonymous(false).Build(),
124 shouldMatch: false,
125 },
126 {
127 name: "custom, wrong value",
128 clause: ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("b")),
129 context: ldcontext.NewBuilder("a").SetString("attr1", "c").Build(),
130 shouldMatch: false,
131 },
132 {
133 name: "custom, no such attribute",
134 clause: ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("b")),
135 context: ldcontext.NewBuilder("a").SetString("attr2", "b").Build(),
136 shouldMatch: false,
137 },
138 {
139
140 name: "custom, no such attribute, negated",
141 clause: ldbuilders.Negate(ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("b"))),
142 context: ldcontext.NewBuilder("a").SetString("attr2", "b").Build(),
143 shouldMatch: false,
144 },
145 {
146 name: "single context value, multiple clause values",
147 clause: ldbuilders.Clause(ldattr.KeyAttr, ldmodel.OperatorIn, ldvalue.String("a"), ldvalue.String("b")),
148 context: ldcontext.New("c"),
149 shouldMatch: false,
150 },
151 {
152 name: "multiple context values, single clause value",
153 clause: ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("c")),
154 context: ldcontext.NewBuilder("a").SetValue("attr1",
155 ldvalue.ArrayOf(ldvalue.String("b"), ldvalue.String("d"))).Build(),
156 shouldMatch: false,
157 },
158 {
159 name: "multiple context values, multiple clause values",
160 clause: ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("c"), ldvalue.String("d")),
161 context: ldcontext.NewBuilder("a").SetValue("attr1",
162 ldvalue.ArrayOf(ldvalue.String("b"), ldvalue.String("e"))).Build(),
163 shouldMatch: false,
164 },
165 {
166 name: "single value match negated",
167 clause: ldbuilders.Negate(ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("b"))),
168 context: ldcontext.NewBuilder("a").SetString("attr1", "b").Build(),
169 shouldMatch: false,
170 },
171 {
172 name: "multi-value match negated",
173 clause: ldbuilders.Negate(
174 ldbuilders.Clause("attr1", ldmodel.OperatorIn, ldvalue.String("b"), ldvalue.String("c"))),
175 context: ldcontext.NewBuilder("a").SetString("attr1", "b").Build(),
176 shouldMatch: false,
177 },
178 {
179 name: "unknown operator",
180 clause: ldbuilders.Clause(ldattr.KeyAttr, "doesSomethingUnsupported", ldvalue.String("a")),
181 context: ldcontext.New("a"),
182 shouldMatch: false,
183 },
184 }
185
186 t.Run("single-kind context of default kind, clause is default kind", func(t *testing.T) {
187 for _, p := range allParams {
188 doClauseMatchTest(t, p)
189 }
190 })
191
192 t.Run("single-kind context of non-default kind, clause is same kind", func(t *testing.T) {
193 for _, p := range allParams {
194 p1 := p
195 p1.clause.ContextKind = "org"
196 p1.context = ldcontext.NewBuilderFromContext(p.context).Kind("org").Build()
197 doClauseMatchTest(t, p1)
198 }
199 })
200
201 t.Run("single-kind context of non-default kind, clause is default kind", func(t *testing.T) {
202 for _, p := range allParams {
203 p1 := p
204 p1.context = ldcontext.NewBuilderFromContext(p.context).Kind("org").Build()
205 p1.shouldMatch = false
206 doClauseMatchTest(t, p1)
207 }
208 })
209
210 t.Run("single-kind context of non-default kind, clause is different non-default kind", func(t *testing.T) {
211 for _, p := range allParams {
212 p1 := p
213 p1.clause.ContextKind = "other"
214 p1.context = ldcontext.NewBuilderFromContext(p.context).Kind("org").Build()
215 p1.shouldMatch = false
216 doClauseMatchTest(t, p1)
217 }
218 })
219
220 t.Run("multi-kind context with default kind, clause is default kind", func(t *testing.T) {
221 for _, p := range allParams {
222 p1 := p
223 p1.context = ldcontext.NewMulti(ldcontext.NewWithKind("org", "x"), p.context)
224 doClauseMatchTest(t, p1)
225 }
226 })
227
228 t.Run("multi-kind context with non-default kind, clause is default kind", func(t *testing.T) {
229 for _, p := range allParams {
230 p1 := p
231 p1.context = ldcontext.NewMulti(ldcontext.NewWithKind("other", "x"),
232 ldcontext.NewBuilderFromContext(p.context).Kind("org").Build())
233 p1.shouldMatch = false
234 doClauseMatchTest(t, p1)
235 }
236 })
237
238 t.Run("multi-kind context with non-default kind, clause is same kind", func(t *testing.T) {
239 for _, p := range allParams {
240 p1 := p
241 p1.clause.ContextKind = "org"
242 p1.context = ldcontext.NewMulti(ldcontext.NewWithKind("other", "x"),
243 ldcontext.NewBuilderFromContext(p.context).Kind("org").Build())
244 doClauseMatchTest(t, p1)
245 }
246 })
247
248 t.Run("multi-kind context with non-default kind, clause is different kind", func(t *testing.T) {
249 for _, p := range allParams {
250 p1 := p
251 p1.clause.ContextKind = "whatever"
252 p1.context = ldcontext.NewMulti(ldcontext.NewWithKind("other", "x"),
253 ldcontext.NewBuilderFromContext(p.context).Kind("org").Build())
254 p1.shouldMatch = false
255 doClauseMatchTest(t, p1)
256 }
257 })
258 }
259
260 func TestClauseMatchOnKindAttribute(t *testing.T) {
261
262
263
264
265
266 allParams := []clauseMatchParams{}
267
268 for _, multiKindContext := range []bool{true, false} {
269 context := ldcontext.New("a")
270 contextDesc := "individual context of default kind"
271 if multiKindContext {
272 contextDesc = "multi-kind context with default kind"
273 context = ldcontext.NewMulti(ldcontext.NewWithKind("irrelevantKind99", "b"), context)
274 }
275 allParams = append(allParams,
276 clauseMatchParams{
277 name: contextDesc + ", clause wants default kind",
278 clause: ldbuilders.Clause(ldattr.KindAttr, ldmodel.OperatorIn, ldvalue.String(string(ldcontext.DefaultKind))),
279 context: context,
280 shouldMatch: true,
281 },
282 clauseMatchParams{
283 name: contextDesc + ", multiple clause values with default kind",
284 clause: ldbuilders.Clause(ldattr.KindAttr, ldmodel.OperatorIn,
285 ldvalue.String("irrelevantKind2"), ldvalue.String(string(ldcontext.DefaultKind))),
286 context: context,
287 shouldMatch: true,
288 },
289 clauseMatchParams{
290 name: contextDesc + ", clause wants different kind",
291 clause: ldbuilders.Clause(ldattr.KindAttr, ldmodel.OperatorIn, ldvalue.String("irrelevantKind2")),
292 context: context,
293 shouldMatch: false,
294 },
295 clauseMatchParams{
296 name: contextDesc + ", multiple clause values without default kind",
297 clause: ldbuilders.Clause(ldattr.KindAttr, ldmodel.OperatorIn,
298 ldvalue.String("irrelevantKind2"), ldvalue.String("irrelevantKind3")),
299 context: context,
300 shouldMatch: false,
301 })
302 }
303
304 for _, multiKindContext := range []bool{true, false} {
305 myContextKind := "org"
306 context := ldcontext.NewWithKind(ldcontext.Kind(myContextKind), "a")
307 contextDesc := "individual context of non-default kind"
308 if multiKindContext {
309 contextDesc = "multi-kind context with non-default kind"
310 context = ldcontext.NewMulti(ldcontext.NewWithKind("irrelevantKind99", "b"), context)
311 }
312 allParams = append(allParams,
313 clauseMatchParams{
314 name: contextDesc + ", clause wants same kind",
315 clause: ldbuilders.Clause(ldattr.KindAttr, ldmodel.OperatorIn, ldvalue.String(myContextKind)),
316 context: context,
317 shouldMatch: true,
318 },
319 clauseMatchParams{
320 name: contextDesc + ", multiple clause values with same kind",
321 clause: ldbuilders.Clause(ldattr.KindAttr, ldmodel.OperatorIn,
322 ldvalue.String("irrelevantKind2"), ldvalue.String("irrelevantKind1"), ldvalue.String(myContextKind)),
323 context: context,
324 shouldMatch: true,
325 },
326 clauseMatchParams{
327 name: contextDesc + ", clause wants different kind",
328 clause: ldbuilders.Clause(ldattr.KindAttr, ldmodel.OperatorIn, ldvalue.String("irrelevantKind2")),
329 context: context,
330 shouldMatch: false,
331 },
332 clauseMatchParams{
333 name: contextDesc + ", multiple clause values without same kind",
334 clause: ldbuilders.Clause(ldattr.KindAttr, ldmodel.OperatorIn,
335 ldvalue.String("irrelevantKind1"), ldvalue.String("irrelevantKind2")),
336 context: context,
337 shouldMatch: false,
338 })
339 }
340
341 t.Run("not negated", func(t *testing.T) {
342 for _, p := range allParams {
343 doClauseMatchTest(t, p)
344 }
345 })
346
347 t.Run("negated", func(t *testing.T) {
348 for _, p := range allParams {
349 p1 := p
350 p1.clause = ldbuilders.Negate(p.clause)
351 p1.shouldMatch = !p.shouldMatch
352 doClauseMatchTest(t, p1)
353 }
354 })
355 }
356
357 func TestClauseMatchErrorConditions(t *testing.T) {
358 t.Run("unspecified attribute", func(t *testing.T) {
359 clause := ldbuilders.ClauseRef(ldattr.Ref{}, ldmodel.OperatorIn, ldvalue.Int(4))
360 context := ldcontext.New("key")
361 match, err := makeEvalScope(context).clauseMatchesContext(&clause, evaluationStack{})
362 assert.Equal(t, emptyAttrRefError{}, err)
363 assert.False(t, match)
364 })
365
366 t.Run("invalid attribute reference", func(t *testing.T) {
367 clause := ldbuilders.ClauseRef(ldattr.NewRef("///"), ldmodel.OperatorIn, ldvalue.Int(4))
368 context := ldcontext.New("key")
369 match, err := makeEvalScope(context).clauseMatchesContext(&clause, evaluationStack{})
370 assert.Equal(t, badAttrRefError("///"), err)
371 assert.False(t, match)
372 })
373 }
374
View as plain text