github.com/crowdsecurity/crowdsec@v1.6.1/pkg/exprhelpers/exprlib_test.go (about)

     1  package exprhelpers
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/antonmedv/expr"
    11  	"github.com/pkg/errors"
    12  	log "github.com/sirupsen/logrus"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/crowdsecurity/go-cs-lib/cstest"
    17  	"github.com/crowdsecurity/go-cs-lib/ptr"
    18  
    19  	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
    20  	"github.com/crowdsecurity/crowdsec/pkg/database"
    21  	"github.com/crowdsecurity/crowdsec/pkg/models"
    22  	"github.com/crowdsecurity/crowdsec/pkg/types"
    23  )
    24  
    25  var (
    26  	TestFolder = "tests"
    27  )
    28  
    29  func getDBClient(t *testing.T) *database.Client {
    30  	t.Helper()
    31  
    32  	dbPath, err := os.CreateTemp("", "*sqlite")
    33  	require.NoError(t, err)
    34  
    35  	testDBClient, err := database.NewClient(&csconfig.DatabaseCfg{
    36  		Type:   "sqlite",
    37  		DbName: "crowdsec",
    38  		DbPath: dbPath.Name(),
    39  	})
    40  	require.NoError(t, err)
    41  
    42  	return testDBClient
    43  }
    44  
    45  func TestVisitor(t *testing.T) {
    46  	err := Init(nil)
    47  	require.NoError(t, err)
    48  
    49  	tests := []struct {
    50  		name   string
    51  		filter string
    52  		result bool
    53  		env    map[string]interface{}
    54  		err    error
    55  	}{
    56  		{
    57  			name:   "debug : no variable",
    58  			filter: "'crowdsec' startsWith 'crowdse'",
    59  			result: true,
    60  			err:    nil,
    61  			env:    map[string]interface{}{},
    62  		},
    63  		{
    64  			name:   "debug : simple variable",
    65  			filter: "'crowdsec' startsWith static_one && 1 == 1",
    66  			result: true,
    67  			err:    nil,
    68  			env:    map[string]interface{}{"static_one": string("crowdse")},
    69  		},
    70  		{
    71  			name:   "debug : simple variable re-used",
    72  			filter: "static_one.foo == 'bar' && static_one.foo != 'toto'",
    73  			result: true,
    74  			err:    nil,
    75  			env:    map[string]interface{}{"static_one": map[string]string{"foo": "bar"}},
    76  		},
    77  		{
    78  			name:   "debug : can't compile",
    79  			filter: "static_one.foo.toto == 'lol'",
    80  			result: false,
    81  			err:    fmt.Errorf("bad syntax"),
    82  			env:    map[string]interface{}{"static_one": map[string]string{"foo": "bar"}},
    83  		},
    84  		{
    85  			name:   "debug : can't compile #2",
    86  			filter: "static_one.f!oo.to/to == 'lol'",
    87  			result: false,
    88  			err:    fmt.Errorf("bad syntax"),
    89  			env:    map[string]interface{}{"static_one": map[string]string{"foo": "bar"}},
    90  		},
    91  		{
    92  			name:   "debug : can't compile #3",
    93  			filter: "",
    94  			result: false,
    95  			err:    fmt.Errorf("bad syntax"),
    96  			env:    map[string]interface{}{"static_one": map[string]string{"foo": "bar"}},
    97  		},
    98  	}
    99  
   100  	log.SetLevel(log.DebugLevel)
   101  
   102  	for _, test := range tests {
   103  		compiledFilter, err := expr.Compile(test.filter, GetExprOptions(test.env)...)
   104  		if err != nil && test.err == nil {
   105  			log.Fatalf("compile: %s", err)
   106  		}
   107  
   108  		if compiledFilter != nil {
   109  			result, err := expr.Run(compiledFilter, test.env)
   110  			if err != nil && test.err == nil {
   111  				log.Fatalf("run : %s", err)
   112  			}
   113  
   114  			if isOk := assert.Equal(t, test.result, result); !isOk {
   115  				t.Fatalf("test '%s' : NOK", test.filter)
   116  			}
   117  		}
   118  	}
   119  }
   120  
   121  func TestMatch(t *testing.T) {
   122  	err := Init(nil)
   123  	require.NoError(t, err)
   124  
   125  	tests := []struct {
   126  		glob string
   127  		val  string
   128  		ret  bool
   129  		expr string
   130  	}{
   131  		{"foo", "foo", true, `Match(pattern, name)`},
   132  		{"foo", "bar", false, `Match(pattern, name)`},
   133  		{"foo*", "foo", true, `Match(pattern, name)`},
   134  		{"foo*", "foobar", true, `Match(pattern, name)`},
   135  		{"foo*", "barfoo", false, `Match(pattern, name)`},
   136  		{"foo*", "bar", false, `Match(pattern, name)`},
   137  		{"*foo", "foo", true, `Match(pattern, name)`},
   138  		{"*foo", "barfoo", true, `Match(pattern, name)`},
   139  		{"foo*r", "foobar", true, `Match(pattern, name)`},
   140  		{"foo*r", "foobazr", true, `Match(pattern, name)`},
   141  		{"foo?ar", "foobar", true, `Match(pattern, name)`},
   142  		{"foo?ar", "foobazr", false, `Match(pattern, name)`},
   143  		{"foo?ar", "foobaz", false, `Match(pattern, name)`},
   144  		{"*foo?ar?", "foobar", false, `Match(pattern, name)`},
   145  		{"*foo?ar?", "foobare", true, `Match(pattern, name)`},
   146  		{"*foo?ar?", "rafoobar", false, `Match(pattern, name)`},
   147  		{"*foo?ar?", "rafoobare", true, `Match(pattern, name)`},
   148  	}
   149  	for _, test := range tests {
   150  		env := map[string]interface{}{
   151  			"pattern": test.glob,
   152  			"name":    test.val,
   153  		}
   154  
   155  		vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
   156  		if err != nil {
   157  			t.Fatalf("pattern:%s val:%s NOK %s", test.glob, test.val, err)
   158  		}
   159  
   160  		ret, err := expr.Run(vm, env)
   161  		require.NoError(t, err)
   162  
   163  		if isOk := assert.Equal(t, test.ret, ret); !isOk {
   164  			t.Fatalf("pattern:%s val:%s NOK %t !=  %t", test.glob, test.val, ret, test.ret)
   165  		}
   166  	}
   167  }
   168  
   169  func TestDistanceHelper(t *testing.T) {
   170  	err := Init(nil)
   171  	require.NoError(t, err)
   172  
   173  	tests := []struct {
   174  		lat1  string
   175  		lon1  string
   176  		lat2  string
   177  		lon2  string
   178  		dist  float64
   179  		valid bool
   180  		expr  string
   181  		name  string
   182  	}{
   183  		{"51.45", "1.15", "41.54", "12.27", 1389.1793118293067, true, `Distance(lat1, lon1, lat2, lon2)`, "valid"},
   184  		{"lol", "1.15", "41.54", "12.27", 0.0, false, `Distance(lat1, lon1, lat2, lon2)`, "invalid lat1"},
   185  		{"0.0", "0.0", "12.1", "12.1", 0.0, true, `Distance(lat1, lon1, lat2, lon2)`, "empty coord"},
   186  	}
   187  
   188  	for _, test := range tests {
   189  		t.Run(test.name, func(t *testing.T) {
   190  			env := map[string]interface{}{
   191  				"lat1": test.lat1,
   192  				"lon1": test.lon1,
   193  				"lat2": test.lat2,
   194  				"lon2": test.lon2,
   195  			}
   196  			vm, err := expr.Compile(test.expr, GetExprOptions(env)...)
   197  			if err != nil {
   198  				t.Fatalf("pattern:%s val:%s NOK %s", test.lat1, test.lon1, err)
   199  			}
   200  			ret, err := expr.Run(vm, env)
   201  			if test.valid {
   202  				require.NoError(t, err)
   203  				assert.InDelta(t, test.dist, ret, 0.000001)
   204  			} else {
   205  				require.Error(t, err)
   206  			}
   207  		})
   208  	}
   209  }
   210  
   211  func TestRegexpCacheBehavior(t *testing.T) {
   212  	err := Init(nil)
   213  	require.NoError(t, err)
   214  
   215  	filename := "test_data_re.txt"
   216  	err = FileInit(TestFolder, filename, "regex")
   217  	require.NoError(t, err)
   218  
   219  	//cache with no TTL
   220  	err = RegexpCacheInit(filename, types.DataSource{Type: "regex", Size: ptr.Of(1)})
   221  	require.NoError(t, err)
   222  
   223  	ret, _ := RegexpInFile("crowdsec", filename)
   224  	assert.False(t, ret.(bool))
   225  	assert.Equal(t, 1, dataFileRegexCache[filename].Len(false))
   226  
   227  	ret, _ = RegexpInFile("Crowdsec", filename)
   228  	assert.True(t, ret.(bool))
   229  	assert.Equal(t, 1, dataFileRegexCache[filename].Len(false))
   230  
   231  	//cache with TTL
   232  	ttl := 500 * time.Millisecond
   233  	err = RegexpCacheInit(filename, types.DataSource{Type: "regex", Size: ptr.Of(2), TTL: &ttl})
   234  	require.NoError(t, err)
   235  
   236  	ret, _ = RegexpInFile("crowdsec", filename)
   237  	assert.False(t, ret.(bool))
   238  	assert.Equal(t, 1, dataFileRegexCache[filename].Len(true))
   239  
   240  	time.Sleep(1 * time.Second)
   241  	assert.Equal(t, 0, dataFileRegexCache[filename].Len(true))
   242  }
   243  
   244  func TestRegexpInFile(t *testing.T) {
   245  	if err := Init(nil); err != nil {
   246  		log.Fatal(err)
   247  	}
   248  
   249  	err := FileInit(TestFolder, "test_data_re.txt", "regex")
   250  	if err != nil {
   251  		log.Fatal(err)
   252  	}
   253  
   254  	tests := []struct {
   255  		name   string
   256  		filter string
   257  		result bool
   258  		err    error
   259  	}{
   260  		{
   261  			name:   "RegexpInFile() test: lower case word in data file",
   262  			filter: "RegexpInFile('crowdsec', 'test_data_re.txt')",
   263  			result: false,
   264  			err:    nil,
   265  		},
   266  		{
   267  			name:   "RegexpInFile() test: Match exactly",
   268  			filter: "RegexpInFile('Crowdsec', 'test_data_re.txt')",
   269  			result: true,
   270  			err:    nil,
   271  		},
   272  		{
   273  			name:   "RegexpInFile() test: match with word before",
   274  			filter: "RegexpInFile('test Crowdsec', 'test_data_re.txt')",
   275  			result: true,
   276  			err:    nil,
   277  		},
   278  		{
   279  			name:   "RegexpInFile() test: match with word before and other case",
   280  			filter: "RegexpInFile('test CrowdSec', 'test_data_re.txt')",
   281  			result: true,
   282  			err:    nil,
   283  		},
   284  	}
   285  
   286  	for _, test := range tests {
   287  		compiledFilter, err := expr.Compile(test.filter, GetExprOptions(map[string]interface{}{})...)
   288  		if err != nil {
   289  			log.Fatal(err)
   290  		}
   291  
   292  		result, err := expr.Run(compiledFilter, map[string]interface{}{})
   293  		if err != nil {
   294  			log.Fatal(err)
   295  		}
   296  
   297  		if isOk := assert.Equal(t, test.result, result); !isOk {
   298  			t.Fatalf("test '%s' : NOK", test.name)
   299  		}
   300  	}
   301  }
   302  
   303  func TestFileInit(t *testing.T) {
   304  	if err := Init(nil); err != nil {
   305  		log.Fatal(err)
   306  	}
   307  
   308  	tests := []struct {
   309  		name     string
   310  		filename string
   311  		types    string
   312  		result   int
   313  		err      error
   314  	}{
   315  		{
   316  			name:     "file with type:string",
   317  			filename: "test_data.txt",
   318  			types:    "string",
   319  			result:   3,
   320  		},
   321  		{
   322  			name:     "file with type:string and empty lines + commentaries",
   323  			filename: "test_empty_line.txt",
   324  			types:    "string",
   325  			result:   3,
   326  		},
   327  		{
   328  			name:     "file with type:re",
   329  			filename: "test_data_re.txt",
   330  			types:    "regex",
   331  			result:   2,
   332  		},
   333  		{
   334  			name:     "file without type",
   335  			filename: "test_data_no_type.txt",
   336  			types:    "",
   337  		},
   338  	}
   339  
   340  	for _, test := range tests {
   341  		err := FileInit(TestFolder, test.filename, test.types)
   342  		if err != nil {
   343  			log.Fatal(err)
   344  		}
   345  
   346  		switch test.types {
   347  		case "string":
   348  			if _, ok := dataFile[test.filename]; !ok {
   349  				t.Fatalf("test '%s' : NOK", test.name)
   350  			}
   351  
   352  			if isOk := assert.Len(t, dataFile[test.filename], test.result); !isOk {
   353  				t.Fatalf("test '%s' : NOK", test.name)
   354  			}
   355  		case "regex":
   356  			if _, ok := dataFileRegex[test.filename]; !ok {
   357  				t.Fatalf("test '%s' : NOK", test.name)
   358  			}
   359  
   360  			if isOk := assert.Len(t, dataFileRegex[test.filename], test.result); !isOk {
   361  				t.Fatalf("test '%s' : NOK", test.name)
   362  			}
   363  		default:
   364  			if _, ok := dataFileRegex[test.filename]; ok {
   365  				t.Fatalf("test '%s' : NOK", test.name)
   366  			}
   367  
   368  			if _, ok := dataFile[test.filename]; ok {
   369  				t.Fatalf("test '%s' : NOK", test.name)
   370  			}
   371  		}
   372  
   373  		log.Printf("test '%s' : OK", test.name)
   374  	}
   375  }
   376  
   377  func TestFile(t *testing.T) {
   378  	if err := Init(nil); err != nil {
   379  		log.Fatal(err)
   380  	}
   381  
   382  	err := FileInit(TestFolder, "test_data.txt", "string")
   383  	if err != nil {
   384  		log.Fatal(err)
   385  	}
   386  
   387  	tests := []struct {
   388  		name   string
   389  		filter string
   390  		result bool
   391  		err    error
   392  	}{
   393  		{
   394  			name:   "File() test: word in file",
   395  			filter: "'Crowdsec' in File('test_data.txt')",
   396  			result: true,
   397  			err:    nil,
   398  		},
   399  		{
   400  			name:   "File() test: word in file but different case",
   401  			filter: "'CrowdSecurity' in File('test_data.txt')",
   402  			result: false,
   403  			err:    nil,
   404  		},
   405  		{
   406  			name:   "File() test: word not in file",
   407  			filter: "'test' in File('test_data.txt')",
   408  			result: false,
   409  			err:    nil,
   410  		},
   411  		{
   412  			name:   "File() test: filepath provided doesn't exist",
   413  			filter: "'test' in File('non_existing_data.txt')",
   414  			result: false,
   415  			err:    nil,
   416  		},
   417  	}
   418  
   419  	for _, test := range tests {
   420  		compiledFilter, err := expr.Compile(test.filter, GetExprOptions(map[string]interface{}{})...)
   421  		if err != nil {
   422  			log.Fatal(err)
   423  		}
   424  
   425  		result, err := expr.Run(compiledFilter, map[string]interface{}{})
   426  		if err != nil {
   427  			log.Fatal(err)
   428  		}
   429  
   430  		if isOk := assert.Equal(t, test.result, result); !isOk {
   431  			t.Fatalf("test '%s' : NOK", test.name)
   432  		}
   433  
   434  		log.Printf("test '%s' : OK", test.name)
   435  	}
   436  }
   437  
   438  func TestIpInRange(t *testing.T) {
   439  	err := Init(nil)
   440  	require.NoError(t, err)
   441  	tests := []struct {
   442  		name   string
   443  		env    map[string]interface{}
   444  		code   string
   445  		result bool
   446  		err    string
   447  	}{
   448  		{
   449  			name: "IpInRange() test: basic test",
   450  			env: map[string]interface{}{
   451  				"ip":      "192.168.0.1",
   452  				"ipRange": "192.168.0.0/24",
   453  			},
   454  			code:   "IpInRange(ip, ipRange)",
   455  			result: true,
   456  			err:    "",
   457  		},
   458  		{
   459  			name: "IpInRange() test: malformed IP",
   460  			env: map[string]interface{}{
   461  				"ip":      "192.168.0",
   462  				"ipRange": "192.168.0.0/24",
   463  			},
   464  			code:   "IpInRange(ip, ipRange)",
   465  			result: false,
   466  			err:    "",
   467  		},
   468  		{
   469  			name: "IpInRange() test: malformed IP range",
   470  			env: map[string]interface{}{
   471  				"ip":      "192.168.0.0/255",
   472  				"ipRange": "192.168.0.0/24",
   473  			},
   474  			code:   "IpInRange(ip, ipRange)",
   475  			result: false,
   476  			err:    "",
   477  		},
   478  	}
   479  
   480  	for _, test := range tests {
   481  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   482  		require.NoError(t, err)
   483  		output, err := expr.Run(program, test.env)
   484  		require.NoError(t, err)
   485  		require.Equal(t, test.result, output)
   486  		log.Printf("test '%s' : OK", test.name)
   487  	}
   488  }
   489  
   490  func TestIpToRange(t *testing.T) {
   491  	err := Init(nil)
   492  	require.NoError(t, err)
   493  	tests := []struct {
   494  		name   string
   495  		env    map[string]interface{}
   496  		code   string
   497  		result string
   498  		err    string
   499  	}{
   500  		{
   501  			name: "IpToRange() test: IPv4",
   502  			env: map[string]interface{}{
   503  				"ip":      "192.168.1.1",
   504  				"netmask": "16",
   505  			},
   506  			code:   "IpToRange(ip, netmask)",
   507  			result: "192.168.0.0/16",
   508  			err:    "",
   509  		},
   510  		{
   511  			name: "IpToRange() test: IPv6",
   512  			env: map[string]interface{}{
   513  				"ip":      "2001:db8::1",
   514  				"netmask": "/64",
   515  			},
   516  			code:   "IpToRange(ip, netmask)",
   517  			result: "2001:db8::/64",
   518  			err:    "",
   519  		},
   520  		{
   521  			name: "IpToRange() test: malformed netmask",
   522  			env: map[string]interface{}{
   523  				"ip":      "192.168.0.1",
   524  				"netmask": "test",
   525  			},
   526  			code:   "IpToRange(ip, netmask)",
   527  			result: "",
   528  			err:    "",
   529  		},
   530  		{
   531  			name: "IpToRange() test: malformed IP",
   532  			env: map[string]interface{}{
   533  				"ip":      "a.b.c.d",
   534  				"netmask": "24",
   535  			},
   536  			code:   "IpToRange(ip, netmask)",
   537  			result: "",
   538  			err:    "",
   539  		},
   540  		{
   541  			name: "IpToRange() test: too high netmask",
   542  			env: map[string]interface{}{
   543  				"ip":      "192.168.1.1",
   544  				"netmask": "35",
   545  			},
   546  			code:   "IpToRange(ip, netmask)",
   547  			result: "",
   548  			err:    "",
   549  		},
   550  	}
   551  
   552  	for _, test := range tests {
   553  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   554  		require.NoError(t, err)
   555  		output, err := expr.Run(program, test.env)
   556  		require.NoError(t, err)
   557  		require.Equal(t, test.result, output)
   558  		log.Printf("test '%s' : OK", test.name)
   559  	}
   560  }
   561  
   562  func TestAtof(t *testing.T) {
   563  	err := Init(nil)
   564  	require.NoError(t, err)
   565  
   566  	tests := []struct {
   567  		name   string
   568  		env    map[string]interface{}
   569  		code   string
   570  		result float64
   571  	}{
   572  		{
   573  			name: "Atof() test: basic test",
   574  			env: map[string]interface{}{
   575  				"testFloat": "1.5",
   576  			},
   577  			code:   "Atof(testFloat)",
   578  			result: 1.5,
   579  		},
   580  		{
   581  			name: "Atof() test: bad float",
   582  			env: map[string]interface{}{
   583  				"testFloat": "1aaa.5",
   584  			},
   585  			code:   "Atof(testFloat)",
   586  			result: 0.0,
   587  		},
   588  	}
   589  
   590  	for _, test := range tests {
   591  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   592  		require.NoError(t, err)
   593  		output, err := expr.Run(program, test.env)
   594  		require.NoError(t, err)
   595  		require.InDelta(t, test.result, output, 0.000001)
   596  	}
   597  }
   598  
   599  func TestUpper(t *testing.T) {
   600  	testStr := "test"
   601  	expectedStr := "TEST"
   602  
   603  	env := map[string]interface{}{
   604  		"testStr": testStr,
   605  	}
   606  
   607  	err := Init(nil)
   608  	require.NoError(t, err)
   609  	vm, err := expr.Compile("Upper(testStr)", GetExprOptions(env)...)
   610  	require.NoError(t, err)
   611  
   612  	out, err := expr.Run(vm, env)
   613  
   614  	require.NoError(t, err)
   615  
   616  	v, ok := out.(string)
   617  	if !ok {
   618  		t.Fatalf("Upper() should return a string")
   619  	}
   620  
   621  	if v != expectedStr {
   622  		t.Fatalf("Upper() should return test in upper case")
   623  	}
   624  }
   625  
   626  func TestTimeNow(t *testing.T) {
   627  	now, _ := TimeNow()
   628  
   629  	ti, err := time.Parse(time.RFC3339, now.(string))
   630  	if err != nil {
   631  		t.Fatalf("Error parsing the return value of TimeNow: %s", err)
   632  	}
   633  
   634  	if -1*time.Until(ti) > time.Second {
   635  		t.Fatalf("TimeNow func should return time.Now().UTC()")
   636  	}
   637  
   638  	log.Printf("test 'TimeNow()' : OK")
   639  }
   640  
   641  func TestParseUri(t *testing.T) {
   642  	tests := []struct {
   643  		name   string
   644  		env    map[string]interface{}
   645  		code   string
   646  		result map[string][]string
   647  		err    string
   648  	}{
   649  		{
   650  			name: "ParseUri() test: basic test",
   651  			env: map[string]interface{}{
   652  				"uri":      "/foo?a=1&b=2",
   653  				"ParseUri": ParseUri,
   654  			},
   655  			code:   "ParseUri(uri)",
   656  			result: map[string][]string{"a": {"1"}, "b": {"2"}},
   657  			err:    "",
   658  		},
   659  		{
   660  			name: "ParseUri() test: no param",
   661  			env: map[string]interface{}{
   662  				"uri":      "/foo",
   663  				"ParseUri": ParseUri,
   664  			},
   665  			code:   "ParseUri(uri)",
   666  			result: map[string][]string{},
   667  			err:    "",
   668  		},
   669  		{
   670  			name: "ParseUri() test: extra question mark",
   671  			env: map[string]interface{}{
   672  				"uri":      "/foo?a=1&b=2?",
   673  				"ParseUri": ParseUri,
   674  			},
   675  			code:   "ParseUri(uri)",
   676  			result: map[string][]string{"a": {"1"}, "b": {"2?"}},
   677  			err:    "",
   678  		},
   679  		{
   680  			name: "ParseUri() test: weird params",
   681  			env: map[string]interface{}{
   682  				"uri":      "/foo?&?&&&&?=123",
   683  				"ParseUri": ParseUri,
   684  			},
   685  			code:   "ParseUri(uri)",
   686  			result: map[string][]string{"?": {"", "123"}},
   687  			err:    "",
   688  		},
   689  		{
   690  			name: "ParseUri() test: bad encoding",
   691  			env: map[string]interface{}{
   692  				"uri":      "/foo?a=%%F",
   693  				"ParseUri": ParseUri,
   694  			},
   695  			code:   "ParseUri(uri)",
   696  			result: map[string][]string{},
   697  			err:    "",
   698  		},
   699  	}
   700  
   701  	for _, test := range tests {
   702  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   703  		require.NoError(t, err)
   704  		output, err := expr.Run(program, test.env)
   705  		require.NoError(t, err)
   706  		require.Equal(t, test.result, output)
   707  		log.Printf("test '%s' : OK", test.name)
   708  	}
   709  }
   710  
   711  func TestQueryEscape(t *testing.T) {
   712  	tests := []struct {
   713  		name   string
   714  		env    map[string]interface{}
   715  		code   string
   716  		result string
   717  		err    string
   718  	}{
   719  		{
   720  			name: "QueryEscape() test: basic test",
   721  			env: map[string]interface{}{
   722  				"uri":         "/foo?a=1&b=2",
   723  				"QueryEscape": QueryEscape,
   724  			},
   725  			code:   "QueryEscape(uri)",
   726  			result: "%2Ffoo%3Fa%3D1%26b%3D2",
   727  			err:    "",
   728  		},
   729  		{
   730  			name: "QueryEscape() test: basic test",
   731  			env: map[string]interface{}{
   732  				"uri":         "/foo?a=1&&b=<>'\"",
   733  				"QueryEscape": QueryEscape,
   734  			},
   735  			code:   "QueryEscape(uri)",
   736  			result: "%2Ffoo%3Fa%3D1%26%26b%3D%3C%3E%27%22",
   737  			err:    "",
   738  		},
   739  	}
   740  
   741  	for _, test := range tests {
   742  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   743  		require.NoError(t, err)
   744  		output, err := expr.Run(program, test.env)
   745  		require.NoError(t, err)
   746  		require.Equal(t, test.result, output)
   747  		log.Printf("test '%s' : OK", test.name)
   748  	}
   749  }
   750  
   751  func TestPathEscape(t *testing.T) {
   752  	tests := []struct {
   753  		name   string
   754  		env    map[string]interface{}
   755  		code   string
   756  		result string
   757  		err    string
   758  	}{
   759  		{
   760  			name: "PathEscape() test: basic test",
   761  			env: map[string]interface{}{
   762  				"uri":        "/foo?a=1&b=2",
   763  				"PathEscape": PathEscape,
   764  			},
   765  			code:   "PathEscape(uri)",
   766  			result: "%2Ffoo%3Fa=1&b=2",
   767  			err:    "",
   768  		},
   769  		{
   770  			name: "PathEscape() test: basic test with more special chars",
   771  			env: map[string]interface{}{
   772  				"uri":        "/foo?a=1&&b=<>'\"",
   773  				"PathEscape": PathEscape,
   774  			},
   775  			code:   "PathEscape(uri)",
   776  			result: "%2Ffoo%3Fa=1&&b=%3C%3E%27%22",
   777  			err:    "",
   778  		},
   779  	}
   780  
   781  	for _, test := range tests {
   782  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   783  		require.NoError(t, err)
   784  		output, err := expr.Run(program, test.env)
   785  		require.NoError(t, err)
   786  		require.Equal(t, test.result, output)
   787  		log.Printf("test '%s' : OK", test.name)
   788  	}
   789  }
   790  
   791  func TestPathUnescape(t *testing.T) {
   792  	tests := []struct {
   793  		name   string
   794  		env    map[string]interface{}
   795  		code   string
   796  		result string
   797  		err    string
   798  	}{
   799  		{
   800  			name: "PathUnescape() test: basic test",
   801  			env: map[string]interface{}{
   802  				"uri":          "%2Ffoo%3Fa=1&b=%3C%3E%27%22",
   803  				"PathUnescape": PathUnescape,
   804  			},
   805  			code:   "PathUnescape(uri)",
   806  			result: "/foo?a=1&b=<>'\"",
   807  			err:    "",
   808  		},
   809  		{
   810  			name: "PathUnescape() test: basic test with more special chars",
   811  			env: map[string]interface{}{
   812  				"uri":          "/$%7Bjndi",
   813  				"PathUnescape": PathUnescape,
   814  			},
   815  			code:   "PathUnescape(uri)",
   816  			result: "/${jndi",
   817  			err:    "",
   818  		},
   819  	}
   820  
   821  	for _, test := range tests {
   822  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   823  		require.NoError(t, err)
   824  		output, err := expr.Run(program, test.env)
   825  		require.NoError(t, err)
   826  		require.Equal(t, test.result, output)
   827  		log.Printf("test '%s' : OK", test.name)
   828  	}
   829  }
   830  
   831  func TestQueryUnescape(t *testing.T) {
   832  	tests := []struct {
   833  		name   string
   834  		env    map[string]interface{}
   835  		code   string
   836  		result string
   837  		err    string
   838  	}{
   839  		{
   840  			name: "QueryUnescape() test: basic test",
   841  			env: map[string]interface{}{
   842  				"uri":           "%2Ffoo%3Fa=1&b=%3C%3E%27%22",
   843  				"QueryUnescape": QueryUnescape,
   844  			},
   845  			code:   "QueryUnescape(uri)",
   846  			result: "/foo?a=1&b=<>'\"",
   847  			err:    "",
   848  		},
   849  		{
   850  			name: "QueryUnescape() test: basic test with more special chars",
   851  			env: map[string]interface{}{
   852  				"uri":           "/$%7Bjndi",
   853  				"QueryUnescape": QueryUnescape,
   854  			},
   855  			code:   "QueryUnescape(uri)",
   856  			result: "/${jndi",
   857  			err:    "",
   858  		},
   859  	}
   860  
   861  	for _, test := range tests {
   862  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   863  		require.NoError(t, err)
   864  		output, err := expr.Run(program, test.env)
   865  		require.NoError(t, err)
   866  		require.Equal(t, test.result, output)
   867  		log.Printf("test '%s' : OK", test.name)
   868  	}
   869  }
   870  
   871  func TestLower(t *testing.T) {
   872  	tests := []struct {
   873  		name   string
   874  		env    map[string]interface{}
   875  		code   string
   876  		result string
   877  		err    string
   878  	}{
   879  		{
   880  			name: "Lower() test: basic test",
   881  			env: map[string]interface{}{
   882  				"name":  "ABCDEFG",
   883  				"Lower": Lower,
   884  			},
   885  			code:   "Lower(name)",
   886  			result: "abcdefg",
   887  			err:    "",
   888  		},
   889  		{
   890  			name: "Lower() test: basic test with more special chars",
   891  			env: map[string]interface{}{
   892  				"name":  "AbcDefG!#",
   893  				"Lower": Lower,
   894  			},
   895  			code:   "Lower(name)",
   896  			result: "abcdefg!#",
   897  			err:    "",
   898  		},
   899  	}
   900  
   901  	for _, test := range tests {
   902  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   903  		require.NoError(t, err)
   904  		output, err := expr.Run(program, test.env)
   905  		require.NoError(t, err)
   906  		require.Equal(t, test.result, output)
   907  		log.Printf("test '%s' : OK", test.name)
   908  	}
   909  }
   910  
   911  func TestGetDecisionsCount(t *testing.T) {
   912  	existingIP := "1.2.3.4"
   913  	unknownIP := "1.2.3.5"
   914  
   915  	ip_sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(existingIP)
   916  	if err != nil {
   917  		t.Errorf("unable to convert '%s' to int: %s", existingIP, err)
   918  	}
   919  
   920  	// Add sample data to DB
   921  	dbClient = getDBClient(t)
   922  
   923  	decision := dbClient.Ent.Decision.Create().
   924  		SetUntil(time.Now().Add(time.Hour)).
   925  		SetScenario("crowdsec/test").
   926  		SetStartIP(start_ip).
   927  		SetStartSuffix(start_sfx).
   928  		SetEndIP(end_ip).
   929  		SetEndSuffix(end_sfx).
   930  		SetIPSize(int64(ip_sz)).
   931  		SetType("ban").
   932  		SetScope("IP").
   933  		SetValue(existingIP).
   934  		SetOrigin("CAPI").
   935  		SaveX(context.Background())
   936  
   937  	if decision == nil {
   938  		require.Error(t, errors.Errorf("Failed to create sample decision"))
   939  	}
   940  
   941  	err = Init(dbClient)
   942  	require.NoError(t, err)
   943  
   944  	tests := []struct {
   945  		name   string
   946  		env    map[string]interface{}
   947  		code   string
   948  		result string
   949  		err    string
   950  	}{
   951  		{
   952  			name: "GetDecisionsCount() test: existing IP count",
   953  			env: map[string]interface{}{
   954  				"Alert": &models.Alert{
   955  					Source: &models.Source{
   956  						Value: &existingIP,
   957  					},
   958  					Decisions: []*models.Decision{
   959  						{
   960  							Value: &existingIP,
   961  						},
   962  					},
   963  				},
   964  			},
   965  			code:   "Sprintf('%d', GetDecisionsCount(Alert.GetValue()))",
   966  			result: "1",
   967  			err:    "",
   968  		},
   969  		{
   970  			name: "GetDecisionsCount() test: unknown IP count",
   971  			env: map[string]interface{}{
   972  				"Alert": &models.Alert{
   973  					Source: &models.Source{
   974  						Value: &unknownIP,
   975  					},
   976  					Decisions: []*models.Decision{
   977  						{
   978  							Value: &unknownIP,
   979  						},
   980  					},
   981  				},
   982  			},
   983  			code:   "Sprintf('%d', GetDecisionsCount(Alert.GetValue()))",
   984  			result: "0",
   985  			err:    "",
   986  		},
   987  	}
   988  
   989  	for _, test := range tests {
   990  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
   991  		require.NoError(t, err)
   992  		output, err := expr.Run(program, test.env)
   993  		require.NoError(t, err)
   994  		require.Equal(t, test.result, output)
   995  		log.Printf("test '%s' : OK", test.name)
   996  	}
   997  }
   998  func TestGetDecisionsSinceCount(t *testing.T) {
   999  	existingIP := "1.2.3.4"
  1000  	unknownIP := "1.2.3.5"
  1001  
  1002  	ip_sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(existingIP)
  1003  	if err != nil {
  1004  		t.Errorf("unable to convert '%s' to int: %s", existingIP, err)
  1005  	}
  1006  	// Add sample data to DB
  1007  	dbClient = getDBClient(t)
  1008  
  1009  	decision := dbClient.Ent.Decision.Create().
  1010  		SetUntil(time.Now().Add(time.Hour)).
  1011  		SetScenario("crowdsec/test").
  1012  		SetStartIP(start_ip).
  1013  		SetStartSuffix(start_sfx).
  1014  		SetEndIP(end_ip).
  1015  		SetEndSuffix(end_sfx).
  1016  		SetIPSize(int64(ip_sz)).
  1017  		SetType("ban").
  1018  		SetScope("IP").
  1019  		SetValue(existingIP).
  1020  		SetOrigin("CAPI").
  1021  		SaveX(context.Background())
  1022  	if decision == nil {
  1023  		require.Error(t, errors.Errorf("Failed to create sample decision"))
  1024  	}
  1025  
  1026  	decision2 := dbClient.Ent.Decision.Create().
  1027  		SetCreatedAt(time.Now().AddDate(0, 0, -1)).
  1028  		SetUntil(time.Now().AddDate(0, 0, -1)).
  1029  		SetScenario("crowdsec/test").
  1030  		SetStartIP(start_ip).
  1031  		SetStartSuffix(start_sfx).
  1032  		SetEndIP(end_ip).
  1033  		SetEndSuffix(end_sfx).
  1034  		SetIPSize(int64(ip_sz)).
  1035  		SetType("ban").
  1036  		SetScope("IP").
  1037  		SetValue(existingIP).
  1038  		SetOrigin("CAPI").
  1039  		SaveX(context.Background())
  1040  
  1041  	if decision2 == nil {
  1042  		require.Error(t, errors.Errorf("Failed to create sample decision"))
  1043  	}
  1044  
  1045  	err = Init(dbClient)
  1046  	require.NoError(t, err)
  1047  
  1048  	tests := []struct {
  1049  		name   string
  1050  		env    map[string]interface{}
  1051  		code   string
  1052  		result string
  1053  		err    string
  1054  	}{
  1055  		{
  1056  			name: "GetDecisionsSinceCount() test: existing IP count since more than 1 day",
  1057  			env: map[string]interface{}{
  1058  				"Alert": &models.Alert{
  1059  					Source: &models.Source{
  1060  						Value: &existingIP,
  1061  					},
  1062  					Decisions: []*models.Decision{
  1063  						{
  1064  							Value: &existingIP,
  1065  						},
  1066  					},
  1067  				},
  1068  			},
  1069  			code:   "Sprintf('%d', GetDecisionsSinceCount(Alert.GetValue(), '25h'))",
  1070  			result: "2",
  1071  			err:    "",
  1072  		},
  1073  		{
  1074  			name: "GetDecisionsSinceCount() test: existing IP count since more than 1 hour",
  1075  			env: map[string]interface{}{
  1076  				"Alert": &models.Alert{
  1077  					Source: &models.Source{
  1078  						Value: &existingIP,
  1079  					},
  1080  					Decisions: []*models.Decision{
  1081  						{
  1082  							Value: &existingIP,
  1083  						},
  1084  					},
  1085  				},
  1086  			},
  1087  			code:   "Sprintf('%d', GetDecisionsSinceCount(Alert.GetValue(), '1h'))",
  1088  			result: "1",
  1089  			err:    "",
  1090  		},
  1091  		{
  1092  			name: "GetDecisionsSinceCount() test: unknown IP count",
  1093  			env: map[string]interface{}{
  1094  				"Alert": &models.Alert{
  1095  					Source: &models.Source{
  1096  						Value: &unknownIP,
  1097  					},
  1098  					Decisions: []*models.Decision{
  1099  						{
  1100  							Value: &unknownIP,
  1101  						},
  1102  					},
  1103  				},
  1104  			},
  1105  			code:   "Sprintf('%d', GetDecisionsSinceCount(Alert.GetValue(), '1h'))",
  1106  			result: "0",
  1107  			err:    "",
  1108  		},
  1109  	}
  1110  
  1111  	for _, test := range tests {
  1112  		program, err := expr.Compile(test.code, GetExprOptions(test.env)...)
  1113  		require.NoError(t, err)
  1114  		output, err := expr.Run(program, test.env)
  1115  		require.NoError(t, err)
  1116  		require.Equal(t, test.result, output)
  1117  		log.Printf("test '%s' : OK", test.name)
  1118  	}
  1119  }
  1120  
  1121  func TestParseUnixTime(t *testing.T) {
  1122  	tests := []struct {
  1123  		name        string
  1124  		value       string
  1125  		expected    time.Time
  1126  		expectedErr string
  1127  	}{
  1128  		{
  1129  			name:     "ParseUnix() test: valid value with milli",
  1130  			value:    "1672239773.3590894",
  1131  			expected: time.Date(2022, 12, 28, 15, 02, 53, 0, time.UTC),
  1132  		},
  1133  		{
  1134  			name:     "ParseUnix() test: valid value without milli",
  1135  			value:    "1672239773",
  1136  			expected: time.Date(2022, 12, 28, 15, 02, 53, 0, time.UTC),
  1137  		},
  1138  		{
  1139  			name:        "ParseUnix() test: invalid input",
  1140  			value:       "AbcDefG!#",
  1141  			expected:    time.Time{},
  1142  			expectedErr: "unable to parse AbcDefG!# as unix timestamp",
  1143  		},
  1144  		{
  1145  			name:        "ParseUnix() test: negative value",
  1146  			value:       "-1000",
  1147  			expected:    time.Time{},
  1148  			expectedErr: "unable to parse -1000 as unix timestamp",
  1149  		},
  1150  	}
  1151  
  1152  	for _, tc := range tests {
  1153  		tc := tc
  1154  		t.Run(tc.name, func(t *testing.T) {
  1155  			output, err := ParseUnixTime(tc.value)
  1156  			cstest.RequireErrorContains(t, err, tc.expectedErr)
  1157  			if tc.expectedErr != "" {
  1158  				return
  1159  			}
  1160  			require.WithinDuration(t, tc.expected, output.(time.Time), time.Second)
  1161  		})
  1162  	}
  1163  }
  1164  
  1165  func TestIsIp(t *testing.T) {
  1166  	if err := Init(nil); err != nil {
  1167  		log.Fatal(err)
  1168  	}
  1169  
  1170  	tests := []struct {
  1171  		name             string
  1172  		expr             string
  1173  		value            string
  1174  		expected         bool
  1175  		expectedBuildErr bool
  1176  	}{
  1177  		{
  1178  			name:     "IsIPV4() test: valid IPv4",
  1179  			expr:     `IsIPV4(value)`,
  1180  			value:    "1.2.3.4",
  1181  			expected: true,
  1182  		},
  1183  		{
  1184  			name:     "IsIPV6() test: valid IPv6",
  1185  			expr:     `IsIPV6(value)`,
  1186  			value:    "1.2.3.4",
  1187  			expected: false,
  1188  		},
  1189  		{
  1190  			name:     "IsIPV6() test: valid IPv6",
  1191  			expr:     `IsIPV6(value)`,
  1192  			value:    "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
  1193  			expected: true,
  1194  		},
  1195  		{
  1196  			name:     "IsIPV4() test: valid IPv6",
  1197  			expr:     `IsIPV4(value)`,
  1198  			value:    "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
  1199  			expected: false,
  1200  		},
  1201  		{
  1202  			name:     "IsIP() test: invalid IP",
  1203  			expr:     `IsIP(value)`,
  1204  			value:    "foo.bar",
  1205  			expected: false,
  1206  		},
  1207  		{
  1208  			name:     "IsIP() test: valid IPv4",
  1209  			expr:     `IsIP(value)`,
  1210  			value:    "1.2.3.4",
  1211  			expected: true,
  1212  		},
  1213  		{
  1214  			name:     "IsIP() test: valid IPv6",
  1215  			expr:     `IsIP(value)`,
  1216  			value:    "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
  1217  			expected: true,
  1218  		},
  1219  		{
  1220  			name:     "IsIPV4() test: invalid IPv4",
  1221  			expr:     `IsIPV4(value)`,
  1222  			value:    "foo.bar",
  1223  			expected: false,
  1224  		},
  1225  		{
  1226  			name:     "IsIPV6() test: invalid IPv6",
  1227  			expr:     `IsIPV6(value)`,
  1228  			value:    "foo.bar",
  1229  			expected: false,
  1230  		},
  1231  		{
  1232  			name:             "IsIPV4() test: invalid type",
  1233  			expr:             `IsIPV4(42)`,
  1234  			value:            "",
  1235  			expected:         false,
  1236  			expectedBuildErr: true,
  1237  		},
  1238  		{
  1239  			name:             "IsIP() test: invalid type",
  1240  			expr:             `IsIP(42)`,
  1241  			value:            "",
  1242  			expected:         false,
  1243  			expectedBuildErr: true,
  1244  		},
  1245  		{
  1246  			name:             "IsIPV6() test: invalid type",
  1247  			expr:             `IsIPV6(42)`,
  1248  			value:            "",
  1249  			expected:         false,
  1250  			expectedBuildErr: true,
  1251  		},
  1252  	}
  1253  
  1254  	for _, tc := range tests {
  1255  		tc := tc
  1256  		t.Run(tc.name, func(t *testing.T) {
  1257  			vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...)
  1258  			if tc.expectedBuildErr {
  1259  				require.Error(t, err)
  1260  				return
  1261  			}
  1262  			require.NoError(t, err)
  1263  			output, err := expr.Run(vm, map[string]interface{}{"value": tc.value})
  1264  			require.NoError(t, err)
  1265  			assert.IsType(t, tc.expected, output)
  1266  			assert.Equal(t, tc.expected, output.(bool))
  1267  		})
  1268  	}
  1269  }
  1270  
  1271  func TestToString(t *testing.T) {
  1272  	err := Init(nil)
  1273  	require.NoError(t, err)
  1274  
  1275  	tests := []struct {
  1276  		name     string
  1277  		value    interface{}
  1278  		expected string
  1279  		expr     string
  1280  	}{
  1281  		{
  1282  			name:     "ToString() test: valid string",
  1283  			value:    "foo",
  1284  			expected: "foo",
  1285  			expr:     `ToString(value)`,
  1286  		},
  1287  		{
  1288  			name:     "ToString() test: valid string",
  1289  			value:    interface{}("foo"),
  1290  			expected: "foo",
  1291  			expr:     `ToString(value)`,
  1292  		},
  1293  		{
  1294  			name:     "ToString() test: invalid type",
  1295  			value:    1,
  1296  			expected: "",
  1297  			expr:     `ToString(value)`,
  1298  		},
  1299  		{
  1300  			name:     "ToString() test: invalid type 2",
  1301  			value:    interface{}(nil),
  1302  			expected: "",
  1303  			expr:     `ToString(value)`,
  1304  		},
  1305  	}
  1306  	for _, tc := range tests {
  1307  		tc := tc
  1308  		t.Run(tc.name, func(t *testing.T) {
  1309  			vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...)
  1310  			require.NoError(t, err)
  1311  			output, err := expr.Run(vm, map[string]interface{}{"value": tc.value})
  1312  			require.NoError(t, err)
  1313  			require.Equal(t, tc.expected, output)
  1314  		})
  1315  	}
  1316  }
  1317  
  1318  func TestB64Decode(t *testing.T) {
  1319  	err := Init(nil)
  1320  	require.NoError(t, err)
  1321  
  1322  	tests := []struct {
  1323  		name               string
  1324  		value              interface{}
  1325  		expected           string
  1326  		expr               string
  1327  		expectedBuildErr   bool
  1328  		expectedRuntimeErr bool
  1329  	}{
  1330  		{
  1331  			name:             "B64Decode() test: valid string",
  1332  			value:            "Zm9v",
  1333  			expected:         "foo",
  1334  			expr:             `B64Decode(value)`,
  1335  			expectedBuildErr: false,
  1336  		},
  1337  		{
  1338  			name:               "B64Decode() test: invalid string",
  1339  			value:              "foo",
  1340  			expected:           "",
  1341  			expr:               `B64Decode(value)`,
  1342  			expectedBuildErr:   false,
  1343  			expectedRuntimeErr: true,
  1344  		},
  1345  		{
  1346  			name:             "B64Decode() test: invalid type",
  1347  			value:            1,
  1348  			expected:         "",
  1349  			expr:             `B64Decode(value)`,
  1350  			expectedBuildErr: true,
  1351  		},
  1352  	}
  1353  	for _, tc := range tests {
  1354  		tc := tc
  1355  		t.Run(tc.name, func(t *testing.T) {
  1356  			vm, err := expr.Compile(tc.expr, GetExprOptions(map[string]interface{}{"value": tc.value})...)
  1357  			if tc.expectedBuildErr {
  1358  				require.Error(t, err)
  1359  				return
  1360  			}
  1361  			require.NoError(t, err)
  1362  			output, err := expr.Run(vm, map[string]interface{}{"value": tc.value})
  1363  			if tc.expectedRuntimeErr {
  1364  				require.Error(t, err)
  1365  				return
  1366  			}
  1367  			require.NoError(t, err)
  1368  			require.Equal(t, tc.expected, output)
  1369  		})
  1370  	}
  1371  }
  1372  
  1373  func TestParseKv(t *testing.T) {
  1374  	err := Init(nil)
  1375  	require.NoError(t, err)
  1376  
  1377  	tests := []struct {
  1378  		name               string
  1379  		value              string
  1380  		expected           map[string]string
  1381  		expr               string
  1382  		expectedBuildErr   bool
  1383  		expectedRuntimeErr bool
  1384  	}{
  1385  		{
  1386  			name:     "ParseKv() test: valid string",
  1387  			value:    "foo=bar",
  1388  			expected: map[string]string{"foo": "bar"},
  1389  			expr:     `ParseKV(value, out, "a")`,
  1390  		},
  1391  		{
  1392  			name:     "ParseKv() test: valid string",
  1393  			value:    "foo=bar bar=foo",
  1394  			expected: map[string]string{"foo": "bar", "bar": "foo"},
  1395  			expr:     `ParseKV(value, out, "a")`,
  1396  		},
  1397  		{
  1398  			name:     "ParseKv() test: valid string",
  1399  			value:    "foo=bar bar=foo foo=foo",
  1400  			expected: map[string]string{"foo": "foo", "bar": "foo"},
  1401  			expr:     `ParseKV(value, out, "a")`,
  1402  		},
  1403  		{
  1404  			name:     "ParseKV() test: quoted string",
  1405  			value:    `foo="bar=toto"`,
  1406  			expected: map[string]string{"foo": "bar=toto"},
  1407  			expr:     `ParseKV(value, out, "a")`,
  1408  		},
  1409  		{
  1410  			name:     "ParseKV() test: empty unquoted string",
  1411  			value:    `foo= bar=toto`,
  1412  			expected: map[string]string{"bar": "toto", "foo": ""},
  1413  			expr:     `ParseKV(value, out, "a")`,
  1414  		},
  1415  		{
  1416  			name:     "ParseKV() test: empty quoted string ",
  1417  			value:    `foo="" bar=toto`,
  1418  			expected: map[string]string{"bar": "toto", "foo": ""},
  1419  			expr:     `ParseKV(value, out, "a")`,
  1420  		},
  1421  	}
  1422  
  1423  	for _, tc := range tests {
  1424  		tc := tc
  1425  		t.Run(tc.name, func(t *testing.T) {
  1426  			outMap := make(map[string]interface{})
  1427  			env := map[string]interface{}{
  1428  				"value": tc.value,
  1429  				"out":   outMap,
  1430  			}
  1431  			vm, err := expr.Compile(tc.expr, GetExprOptions(env)...)
  1432  			require.NoError(t, err)
  1433  			_, err = expr.Run(vm, env)
  1434  			require.NoError(t, err)
  1435  			assert.Equal(t, tc.expected, outMap["a"])
  1436  		})
  1437  	}
  1438  }