1 package evaluation
2
3 import (
4 "fmt"
5 "testing"
6
7 "github.com/launchdarkly/go-sdk-common/v3/ldcontext"
8 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
9 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
10 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
11
12 "github.com/stretchr/testify/assert"
13 )
14
15 const dateStr1 = "2017-12-06T00:00:00.000-07:00"
16 const dateStr2 = "2017-12-06T00:01:01.000-07:00"
17 const dateMs1 = 10000000
18 const dateMs2 = 10000001
19 const invalidDate = "hey what's this?"
20
21 type opTestInfo struct {
22 opName ldmodel.Operator
23 userValue interface{}
24 clauseValue interface{}
25 moreClauseValues []interface{}
26 expected bool
27 }
28
29 var operatorTests = []opTestInfo{
30
31 {"in", int(99), int(99), nil, true},
32 {"in", int(99), int(99), []interface{}{int(98), int(97), int(96)}, true},
33 {"in", float64(99.0001), float64(99.0001), nil, true},
34 {"in", float64(99.0001), float64(99.0001), []interface{}{float64(98), float64(97), float64(96)}, true},
35 {"lessThan", int(1), float64(1.99999), nil, true},
36 {"lessThan", float64(1.99999), int(1), nil, false},
37 {"lessThan", int(1), uint(2), nil, true},
38 {"lessThanOrEqual", int(1), float64(1), nil, true},
39 {"greaterThan", int(2), float64(1.99999), nil, true},
40 {"greaterThan", float64(1.99999), int(2), nil, false},
41 {"greaterThan", int(2), uint(1), nil, true},
42 {"greaterThanOrEqual", int(1), float64(1), nil, true},
43
44
45 {"in", "x", "x", nil, true},
46 {"in", "x", "x", []interface{}{"a", "b", "c"}, true},
47 {"in", "x", "xyz", nil, false},
48 {"startsWith", "xyz", "x", nil, true},
49 {"startsWith", "x", "xyz", nil, false},
50 {"endsWith", "xyz", "z", nil, true},
51 {"endsWith", "z", "xyz", nil, false},
52 {"contains", "xyz", "y", nil, true},
53 {"contains", "y", "xyz", nil, false},
54
55
56 {"in", "99", int(99), nil, false},
57 {"in", int(99), "99", nil, false},
58 {"contains", "99", int(99), nil, false},
59 {"startsWith", "99", int(99), nil, false},
60 {"endsWith", "99", int(99), nil, false},
61 {"lessThanOrEqual", "99", int(99), nil, false},
62 {"lessThanOrEqual", int(99), "99", nil, false},
63 {"greaterThanOrEqual", "99", int(99), nil, false},
64 {"greaterThanOrEqual", int(99), "99", nil, false},
65
66
67 {"in", true, true, nil, true},
68 {"in", false, false, nil, true},
69 {"in", true, false, nil, false},
70 {"in", false, true, nil, false},
71 {"in", true, false, []interface{}{true}, true},
72
73
74 {"matches", "hello world", "hello.*rld", nil, true},
75 {"matches", "hello world", "hello.*orl", nil, true},
76 {"matches", "hello world", "l+", nil, true},
77 {"matches", "hello world", "(world|planet)", nil, true},
78 {"matches", "hello world", "aloha", nil, false},
79 {"matches", "hello world", "***bad regex", nil, false},
80
81
82 {"before", dateStr1, dateStr2, nil, true},
83 {"before", dateMs1, dateMs2, nil, true},
84 {"before", dateStr2, dateStr1, nil, false},
85 {"before", dateMs2, dateMs1, nil, false},
86 {"before", dateStr1, dateStr1, nil, false},
87 {"before", dateMs1, dateMs1, nil, false},
88 {"before", nil, dateStr1, nil, false},
89 {"before", dateStr1, invalidDate, nil, false},
90 {"after", dateStr2, dateStr1, nil, true},
91 {"after", dateMs2, dateMs1, nil, true},
92 {"after", dateStr1, dateStr2, nil, false},
93 {"after", dateMs1, dateMs2, nil, false},
94 {"after", dateStr1, dateStr1, nil, false},
95 {"after", dateMs1, dateMs1, nil, false},
96 {"after", nil, dateStr1, nil, false},
97 {"after", dateStr1, invalidDate, nil, false},
98
99
100 {"semVerEqual", "2.0.0", "2.0.0", nil, true},
101 {"semVerEqual", "2.0", "2.0.0", nil, true},
102 {"semVerEqual", "2-rc1", "2.0.0-rc1", nil, true},
103 {"semVerEqual", "2+build2", "2.0.0+build2", nil, true},
104 {"semVerEqual", "2.0.0", "2.0.1", nil, false},
105 {"semVerLessThan", "2.0.0", "2.0.1", nil, true},
106 {"semVerLessThan", "2.0", "2.0.1", nil, true},
107 {"semVerLessThan", "2.0.1", "2.0.0", nil, false},
108 {"semVerLessThan", "2.0.1", "2.0", nil, false},
109 {"semVerLessThan", "2.0.1", "xbad%ver", nil, false},
110 {"semVerLessThan", "2.0.0-rc", "2.0.0-rc.beta", nil, true},
111 {"semVerGreaterThan", "2.0.1", "2.0", nil, true},
112 {"semVerGreaterThan", "10.0.1", "2.0", nil, true},
113 {"semVerGreaterThan", "2.0.0", "2.0.1", nil, false},
114 {"semVerGreaterThan", "2.0", "2.0.1", nil, false},
115 {"semVerGreaterThan", "2.0.1", "xbad%ver", nil, false},
116 {"semVerGreaterThan", "2.0.0-rc.1", "2.0.0-rc.0", nil, true},
117
118
119 {"whatever", "x", "x", nil, false},
120 }
121
122 func TestAllOperators(t *testing.T) {
123 userAttr := "attr"
124 for _, ti := range operatorTests {
125 for _, withPreprocessing := range []bool{false, true} {
126 t.Run(
127 fmt.Sprintf("%v %s %v should be %v (preprocess: %t)", ti.userValue, ti.opName, ti.clauseValue, ti.expected, withPreprocessing),
128 func(t *testing.T) {
129 uValue := ldvalue.CopyArbitraryValue(ti.userValue)
130 cValue := ldvalue.CopyArbitraryValue(ti.clauseValue)
131 c := ldbuilders.Clause(userAttr, ti.opName)
132 for _, v := range ti.moreClauseValues {
133 c.Values = append(c.Values, ldvalue.CopyArbitraryValue(v))
134 }
135 c.Values = append(c.Values, cValue)
136 if withPreprocessing {
137 flag := ldmodel.FeatureFlag{
138 Rules: []ldmodel.FlagRule{
139 {Clauses: []ldmodel.Clause{c}},
140 },
141 }
142 ldmodel.PreprocessFlag(&flag)
143 c = flag.Rules[0].Clauses[0]
144 }
145 context := ldcontext.NewBuilder("key").SetValue(userAttr, uValue).Build()
146 isMatch, err := makeEvalScope(context).clauseMatchesContext(&c, evaluationStack{})
147 assert.NoError(t, err)
148 assert.Equal(t, ti.expected, isMatch)
149 },
150 )
151 }
152 }
153 }
154
View as plain text