...

Source file src/github.com/launchdarkly/go-server-sdk-evaluation/v2/evaluator_clause_operator_test.go

Documentation: github.com/launchdarkly/go-server-sdk-evaluation/v2

     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  	// numeric operators
    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  	// string operators
    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  	// mixed strings and numbers
    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  	// equality of boolean values
    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  	// regex
    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  	// date operators
    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  	// semver operators
   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  	// invalid operator
   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