github.com/blend/go-sdk@v1.20220411.3/selector/parse_test.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package selector
     9  
    10  import (
    11  	"testing"
    12  
    13  	"github.com/blend/go-sdk/assert"
    14  	"github.com/blend/go-sdk/ex"
    15  )
    16  
    17  func TestMustParse(t *testing.T) {
    18  	assert := assert.New(t)
    19  
    20  	assert.Equal("x == a", MustParse("x==a").String())
    21  
    22  	var err error
    23  	func() {
    24  		defer func() {
    25  			if r := recover(); r != nil {
    26  				err = ex.New(r)
    27  			}
    28  		}()
    29  		MustParse("x!!")
    30  	}()
    31  	assert.NotNil(err)
    32  }
    33  
    34  func TestParseInvalid(t *testing.T) {
    35  	assert := assert.New(t)
    36  
    37  	testBadStrings := []string{
    38  		"x=a||y=b",
    39  		"x==a==b",
    40  		"!x=a",
    41  		"x<a",
    42  		"x>1",
    43  		"x>1,z<5",
    44  		"x=",
    45  		"x= ",
    46  		"x=,z= ",
    47  		"x= ,z= ",
    48  		"foo == bar foo",
    49  	}
    50  	var err error
    51  	for _, str := range testBadStrings {
    52  		_, err = Parse(str)
    53  		assert.NotNil(err, str)
    54  	}
    55  }
    56  
    57  func TestParseSemiValid(t *testing.T) {
    58  	assert := assert.New(t)
    59  
    60  	testGoodStrings := []string{
    61  		"",
    62  		"x=a,y=b,z=c",
    63  		"x!=a,y=b",
    64  		"x",
    65  		"!x",
    66  		"!x,y",
    67  		"!x,y,z",
    68  		"!x,y,!z",
    69  		"!x,y,z,a",
    70  		"!x,y,z,!a",
    71  	}
    72  
    73  	var err error
    74  	for _, str := range testGoodStrings {
    75  		_, err = Parse(str)
    76  		assert.Nil(err, str)
    77  	}
    78  }
    79  
    80  func TestParseTrailingComma(t *testing.T) {
    81  	assert := assert.New(t)
    82  
    83  	testBadStrings := []string{
    84  		",",
    85  		"x,",
    86  		"!x,",
    87  		"foo==bar,",
    88  		"foo!=bar,",
    89  		"foo in (bar,baz),",
    90  		"foo not in (bar,baz),",
    91  	}
    92  
    93  	var err error
    94  	for _, str := range testBadStrings {
    95  		_, err = Parse(str)
    96  		assert.NotNil(err, str)
    97  	}
    98  }
    99  
   100  func TestParseEquals(t *testing.T) {
   101  	assert := assert.New(t)
   102  
   103  	valid := Labels{
   104  		"foo": "bar",
   105  		"moo": "lar",
   106  	}
   107  	invalid := Labels{
   108  		"zoo": "mar",
   109  		"moo": "lar",
   110  	}
   111  
   112  	selector, err := Parse("foo == bar")
   113  	assert.Nil(err)
   114  	assert.True(selector.Matches(valid))
   115  	assert.False(selector.Matches(invalid))
   116  }
   117  
   118  func TestParseNotEquals(t *testing.T) {
   119  	assert := assert.New(t)
   120  
   121  	valid := Labels{
   122  		"foo": "far",
   123  		"moo": "lar",
   124  	}
   125  	invalidPresent := Labels{
   126  		"foo": "bar",
   127  		"moo": "lar",
   128  	}
   129  	invalidMissing := Labels{
   130  		"zoo": "mar",
   131  		"moo": "lar",
   132  	}
   133  
   134  	selector, err := Parse("foo != bar")
   135  	assert.Nil(err)
   136  	assert.True(selector.Matches(valid))
   137  	assert.True(selector.Matches(invalidMissing))
   138  	assert.False(selector.Matches(invalidPresent))
   139  }
   140  
   141  func TestParseIn(t *testing.T) {
   142  	assert := assert.New(t)
   143  
   144  	valid := Labels{
   145  		"foo": "far",
   146  		"moo": "lar",
   147  	}
   148  	valid2 := Labels{
   149  		"foo": "bar",
   150  		"moo": "lar",
   151  	}
   152  	invalid := Labels{
   153  		"foo": "mar",
   154  		"moo": "lar",
   155  	}
   156  	invalidMissing := Labels{
   157  		"zoo": "mar",
   158  		"moo": "lar",
   159  	}
   160  
   161  	selector, err := Parse("foo in (bar,far)")
   162  	assert.Nil(err)
   163  	assert.True(selector.Matches(valid), selector.String())
   164  	assert.True(selector.Matches(valid2))
   165  	assert.False(selector.Matches(invalidMissing))
   166  	assert.False(selector.Matches(invalid), selector.String())
   167  }
   168  
   169  func TestParseGroup(t *testing.T) {
   170  	assert := assert.New(t)
   171  
   172  	valid := Labels{
   173  		"zoo":   "mar",
   174  		"moo":   "lar",
   175  		"thing": "map",
   176  	}
   177  	invalid := Labels{
   178  		"zoo":   "mar",
   179  		"moo":   "something",
   180  		"thing": "map",
   181  	}
   182  	invalid2 := Labels{
   183  		"zoo":    "mar",
   184  		"moo":    "lar",
   185  		"!thing": "map",
   186  	}
   187  	selector, err := Parse("zoo=mar, moo=lar, thing")
   188  	assert.Nil(err)
   189  	assert.True(selector.Matches(valid))
   190  	assert.False(selector.Matches(invalid))
   191  	assert.False(selector.Matches(invalid2))
   192  
   193  	complicated, err := Parse("zoo in (mar,lar,dar),moo,!thingy")
   194  	assert.Nil(err)
   195  	assert.NotNil(complicated)
   196  	assert.True(complicated.Matches(valid))
   197  }
   198  
   199  func TestParseGroupComplicated(t *testing.T) {
   200  	assert := assert.New(t)
   201  	valid := Labels{
   202  		"zoo":   "mar",
   203  		"moo":   "lar",
   204  		"thing": "map",
   205  	}
   206  	complicated, err := Parse("zoo in (mar,lar,dar),moo,thing == map,!thingy")
   207  	assert.Nil(err)
   208  	assert.NotNil(complicated)
   209  	assert.True(complicated.Matches(valid))
   210  }
   211  
   212  func TestParseDocsExample(t *testing.T) {
   213  	assert := assert.New(t)
   214  	sel, err := Parse("x in (foo,,baz),y,z notin ()")
   215  	assert.Nil(err)
   216  	assert.NotNil(sel)
   217  }
   218  
   219  func TestParseSubdomainKey(t *testing.T) {
   220  
   221  	assert := assert.New(t)
   222  	sel, err := Parse("example.com/failure-domain == primary")
   223  	assert.Nil(err)
   224  	assert.NotNil(sel)
   225  	assert.Equal("example.com/failure-domain == primary", sel.String())
   226  	assert.True(sel.Matches(map[string]string{
   227  		"bar":                        "foo",
   228  		"example.com/failure-domain": "primary",
   229  		"foo":                        "bar",
   230  	}))
   231  }
   232  
   233  func TestParseEqualsOperators(t *testing.T) {
   234  	assert := assert.New(t)
   235  
   236  	selector, err := Parse("notin=in")
   237  	assert.Nil(err)
   238  
   239  	typed, isTyped := selector.(Equals)
   240  	assert.True(isTyped)
   241  	assert.Equal("notin", typed.Key)
   242  	assert.Equal("in", typed.Value)
   243  }
   244  
   245  func TestParseValidate(t *testing.T) {
   246  	assert := assert.New(t)
   247  
   248  	_, err := Parse("zoo=bar")
   249  	assert.Nil(err)
   250  
   251  	_, err = Parse("_zoo=bar")
   252  	assert.NotNil(err)
   253  
   254  	_, err = Parse("_zoo=_bar")
   255  	assert.NotNil(err)
   256  
   257  	_, err = Parse("zoo=bar,foo=_mar")
   258  	assert.NotNil(err)
   259  }
   260  
   261  func TestParseRegressionCSVSymbols(t *testing.T) {
   262  	assert := assert.New(t)
   263  
   264  	sel, err := Parse("foo in (bar-bar, baz.baz, buzz_buzz), moo=boo")
   265  	assert.Nil(err, "regression is csv values can have '-', '.' and '_' in them")
   266  	assert.NotEmpty(sel.String())
   267  }
   268  
   269  func TestParseRegressionCSVEarlyTermination(t *testing.T) {
   270  	assert := assert.New(t)
   271  
   272  	sel, err := Parse("foo in (bar,), moo=boo")
   273  	assert.Nil(err)
   274  	assert.NotEmpty(sel.String())
   275  }
   276  
   277  func TestParseRegressionCSVMultipleSpaces(t *testing.T) {
   278  	assert := assert.New(t)
   279  
   280  	sel, err := Parse("foo in (bar,  foo), moo=boo")
   281  	assert.Nil(err)
   282  	assert.NotEmpty(sel.String())
   283  }
   284  
   285  func TestParseRegressionIn(t *testing.T) {
   286  	assert := assert.New(t)
   287  
   288  	_, err := Parse("foo in bar, buzz)")
   289  	assert.NotNil(err)
   290  }
   291  
   292  func TestParseMultiByte(t *testing.T) {
   293  	assert := assert.New(t)
   294  
   295  	selector, err := Parse("함=수,목=록") // number=number, number=rock
   296  	assert.Nil(err)
   297  	assert.NotNil(selector)
   298  
   299  	typed, isTyped := selector.(And)
   300  	assert.True(isTyped)
   301  	assert.Len(typed, 2)
   302  }
   303  
   304  func TestParseOptions(t *testing.T) {
   305  	assert := assert.New(t)
   306  
   307  	selQuery := "bar=foo@bar"
   308  	labels := Labels{
   309  		"foo": "bar",
   310  		"bar": "foo@bar",
   311  	}
   312  
   313  	sel, err := Parse(selQuery)
   314  	assert.NotNil(err)
   315  	assert.Nil(sel)
   316  
   317  	sel, err = Parse(selQuery, SkipValidation)
   318  	assert.Nil(err)
   319  	assert.NotNil(sel)
   320  
   321  	assert.True(sel.Matches(labels))
   322  }
   323  
   324  func BenchmarkParse(b *testing.B) {
   325  	valid := Labels{
   326  		"zoo":   "mar",
   327  		"moo":   "lar",
   328  		"thing": "map",
   329  	}
   330  
   331  	for i := 0; i < b.N; i++ {
   332  		selector, err := Parse("zoo in (mar,lar,dar),moo,!thingy")
   333  		if err != nil {
   334  			b.Fail()
   335  		}
   336  		if !selector.Matches(valid) {
   337  			b.Fail()
   338  		}
   339  	}
   340  }
   341  
   342  func TestParse_FuzzRegressions(t *testing.T) {
   343  	assert := assert.New(t)
   344  
   345  	var sel Selector
   346  	var err error
   347  	testBadStrings := []string{
   348  		"!0!0",
   349  		"0!=0,!",
   350  	}
   351  	for _, str := range testBadStrings {
   352  		_, err = Parse(str)
   353  		assert.NotNil(err, str, err)
   354  	}
   355  
   356  	testGoodStrings := []string{
   357  		"0,!0",
   358  		"0 in (0), !0",
   359  	}
   360  	for _, str := range testGoodStrings {
   361  		sel, err = Parse(str)
   362  		assert.Nil(err)
   363  		_, err = Parse(sel.String())
   364  		assert.Nil(err)
   365  	}
   366  }
   367  
   368  func TestParse_InRegression(t *testing.T) {
   369  	its := assert.New(t)
   370  
   371  	good0 := Labels{
   372  		"role": "job",
   373  		"team": "internal-engineering",
   374  	}
   375  	good1 := Labels{
   376  		"role": "job-worker",
   377  		"team": "internal-engineering",
   378  	}
   379  	bad0 := Labels{
   380  		"role": "not-job",
   381  	}
   382  
   383  	sel, err := Parse("role in (job, job-worker),team=internal-engineering")
   384  	its.Nil(err)
   385  
   386  	its.True(sel.Matches(good0))
   387  	its.True(sel.Matches(good1))
   388  	its.False(sel.Matches(bad0))
   389  }