...

Source file src/github.com/launchdarkly/go-server-sdk-evaluation/v2/evaluator_clause_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/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  // The tests in this file cover the logical behavior of clause matching *except* for the operator. The
    18  // behavior of individual operators is covered in detail in evaluator_clause_operator_test.go, so the
    19  // tests in this file all use the "in" operator on the assumption that the choice of operator does not
    20  // affect the other aspects of clause behavior.
    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  		// cases that should match as long as the context kind is right
    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  		// cases that should never match
   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  			// If the attribute does not exist then the clause is a non-match no matter what - even if negated
   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  	// This is a separate test suite because the rules are a little different when Attribute is
   262  	// "kind"-- the clause's ContextKind field is irrelevant in that case, so we don't run
   263  	// permutations of these parameters with different values of that field as we do in
   264  	// TestClauseMatch.
   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