From 966f997d2eef18412edb101698e4196017074f41 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 2 May 2023 09:46:55 +0200 Subject: [PATCH] Add extended filter/parser tests & adjust expected error messages --- internal/filter/parser_test.go | 356 +++++++++++++++++++++++++++------ internal/object/object_test.go | 4 +- 2 files changed, 299 insertions(+), 61 deletions(-) diff --git a/internal/filter/parser_test.go b/internal/filter/parser_test.go index fc173d539..c11d39498 100644 --- a/internal/filter/parser_test.go +++ b/internal/filter/parser_test.go @@ -10,57 +10,79 @@ import ( func TestParser(t *testing.T) { t.Parallel() - t.Run("MissingLogicalOperatorsAfterConditionsAreDetected", func(t *testing.T) { - _, err := Parse("(a=b|c=d)e=f") - - expected := "invalid filter '(a=b|c=d)e=f', unexpected e at pos 10: Expected logical operator" - assert.EqualError(t, err, expected, "Errors should be the same") - }) + t.Run("ParseInvalidFilters", func(t *testing.T) { + t.Parallel() - t.Run("MissingLogicalOperatorsAfterOperatorsAreDetected", func(t *testing.T) { - _, err := Parse("(a=b|c=d|)e=f") + _, err := Parse("(a=b|c=d)e=f") + assert.EqualError(t, err, "1:10 (9): syntax error: unexpected T_IDENTIFIER") - expected := "invalid filter '(a=b|c=d|)e=f', unexpected e at pos 11: Expected logical operator" - assert.EqualError(t, err, expected, "Errors should be the same") - }) + _, err = Parse("(a=b|c=d|)e=f") + assert.EqualError(t, err, "1:10 (9): syntax error: unexpected \")\", expecting T_STRING or T_IDENTIFIER or \"(\"") - t.Run("ParserIdentifiesInvalidExpression", func(t *testing.T) { - _, err := Parse("col=(") - assert.EqualError(t, err, "invalid filter 'col=(', unexpected ( at pos 5", "Errors should be the same") + _, err = Parse("col=(") + assert.EqualError(t, err, "1:5 (4): syntax error: unexpected \"(\", expecting T_STRING or T_IDENTIFIER") _, err = Parse("(((x=a)&y=b") - assert.EqualError(t, err, "invalid filter '(((x=a)&y=b', missing 2 closing ')' at pos 11", "Errors should be the same") + assert.EqualError(t, err, "1:12 (11): syntax error: unexpected $end, expecting \")\"") _, err = Parse("(x=a)&y=b)") - assert.EqualError(t, err, "invalid filter '(x=a)&y=b)', unexpected ) at pos 10", "Errors should be the same") + assert.EqualError(t, err, "1:10 (9): syntax error: unexpected \")\"") _, err = Parse("!(&") - assert.EqualError(t, err, "invalid filter '!(&', unexpected & at pos 3", "Errors should be the same") - - _, err = Parse("!(!&") - assert.EqualError(t, err, "invalid filter '!(!&', unexpected & at pos 4: operator level 1", "Errors should be the same") - - _, err = Parse("!(|test") - assert.EqualError(t, err, "invalid filter '!(|test', unexpected | at pos 3", "Errors should be the same") + assert.EqualError(t, err, "1:3 (2): syntax error: unexpected \"&\", expecting T_STRING or T_IDENTIFIER or \"(\"") _, err = Parse("foo&bar=(te(st)") - assert.EqualError(t, err, "invalid filter 'foo&bar=(te(st)', unexpected ( at pos 9", "Errors should be the same") + assert.EqualError(t, err, "1:9 (8): syntax error: unexpected \"(\", expecting T_STRING or T_IDENTIFIER") _, err = Parse("foo&bar=te(st)") - assert.EqualError(t, err, "invalid filter 'foo&bar=te(st)', unexpected ( at pos 11", "Errors should be the same") + assert.EqualError(t, err, "1:11 (10): syntax error: unexpected \"(\"") _, err = Parse("foo&bar=test)") - assert.EqualError(t, err, "invalid filter 'foo&bar=test)', unexpected ) at pos 13", "Errors should be the same") + assert.EqualError(t, err, "1:13 (12): syntax error: unexpected \")\"") _, err = Parse("!()|&()&)") - assert.EqualError(t, err, "invalid filter '!()|&()&)', unexpected closing ')' at pos 9", "Errors should be the same") + assert.EqualError(t, err, "1:3 (2): syntax error: unexpected \")\", expecting T_STRING or T_IDENTIFIER or \"(\"") + + _, err = Parse("=foo") + assert.EqualError(t, err, "1:1 (0): syntax error: unexpected T_EQUAL, expecting T_STRING or T_IDENTIFIER or \"(\"") + + _, err = Parse("foo>") + assert.EqualError(t, err, "1:5 (4): syntax error: unexpected $end, expecting T_STRING or T_IDENTIFIER") + + _, err = Parse("foo==") + assert.EqualError(t, err, "1:5 (4): syntax error: unexpected T_EQUAL, expecting T_STRING or T_IDENTIFIER") + + _, err = Parse("=>foo") + assert.EqualError(t, err, "1:1 (0): syntax error: unexpected T_EQUAL, expecting T_STRING or T_IDENTIFIER or \"(\"") + + _, err = Parse("&foo") + assert.EqualError(t, err, "1:1 (0): syntax error: unexpected \"&\", expecting T_STRING or T_IDENTIFIER or \"(\"") + + _, err = Parse("&&foo") + assert.EqualError(t, err, "1:1 (0): syntax error: unexpected \"&\", expecting T_STRING or T_IDENTIFIER or \"(\"") + + _, err = Parse("(&foo=bar)") + assert.EqualError(t, err, "1:2 (1): syntax error: unexpected \"&\", expecting T_STRING or T_IDENTIFIER or \"(\"") + + _, err = Parse("(foo=bar|)") + assert.EqualError(t, err, "1:10 (9): syntax error: unexpected \")\", expecting T_STRING or T_IDENTIFIER or \"(\"") + + _, err = Parse("((((((") + assert.EqualError(t, err, "1:7 (6): syntax error: unexpected $end, expecting T_STRING or T_IDENTIFIER or \"(\"") + + _, err = Parse("foo&bar&col=val!=val") + assert.EqualError(t, err, "1:17 (16): syntax error: unexpected T_UNEQUAL") + + _, err = Parse("col%7umn") + assert.EqualError(t, err, "invalid URL escape \"%7u\"") + + _, err = Parse("((0&((((((((((((((((((((((0=0)") + assert.EqualError(t, err, "1:31 (30): syntax error: unexpected $end, expecting \")\"") }) -} -func TestFilter(t *testing.T) { - t.Parallel() + t.Run("ParseAllKindOfSimpleFilters", func(t *testing.T) { + t.Parallel() - t.Run("ParserIdentifiesAllKindOfFilters", func(t *testing.T) { rule, err := Parse("foo=bar") assert.Nil(t, err, "There should be no errors but got: %s", err) assert.IsType(t, &Equal{}, rule) @@ -69,11 +91,11 @@ func TestFilter(t *testing.T) { assert.Nil(t, err, "There should be no errors but got: %s", err) assert.IsType(t, &UnEqual{}, rule) - rule, err = Parse("foo=bar*") + rule, err = Parse("foo~bar*") assert.Nil(t, err, "There should be no errors but got: %s", err) assert.IsType(t, &Like{}, rule) - rule, err = Parse("foo!=bar*") + rule, err = Parse("foo!~bar*") assert.Nil(t, err, "There should be no errors but got: %s", err) assert.IsType(t, &Unlike{}, rule) @@ -93,60 +115,261 @@ func TestFilter(t *testing.T) { assert.Nil(t, err, "There should be no errors but got: %s", err) assert.IsType(t, &GreaterThanOrEqual{}, rule) + rule, err = Parse("foo") + assert.Nil(t, err, "There should be no errors but got: %s", err) + assert.Equal(t, &Exists{column: "foo"}, rule) + + rule, err = Parse("!foo") + assert.Nil(t, err, "There should be no errors but got: %s", err) + expected := &Chain{op: NONE, rules: []Filter{&Exists{column: "foo"}}} + assert.Equal(t, expected, rule) + + rule, err = Parse("!foo=bar") + assert.Nil(t, err, "There should be no errors but got: %s", err) + expected = &Chain{op: NONE, rules: []Filter{&Equal{column: "foo", value: "bar"}}} + assert.Equal(t, expected, rule) + rule, err = Parse("foo=bar&bar=foo") assert.Nil(t, err, "There should be no errors but got: %s", err) - assert.IsType(t, &All{}, rule) + expected = &Chain{op: ALL, rules: []Filter{ + &Equal{column: "foo", value: "bar"}, + &Equal{column: "bar", value: "foo"}, + }} + assert.Equal(t, expected, rule) + + rule, err = Parse("foo=bar&bar=foo|col=val") + assert.Nil(t, err, "There should be no errors but got: %s", err) + expected = &Chain{op: ANY, rules: []Filter{ + &Chain{op: ALL, rules: []Filter{ + &Equal{column: "foo", value: "bar"}, + &Equal{column: "bar", value: "foo"}, + }}, + &Equal{column: "col", value: "val"}, + }} + assert.Equal(t, expected, rule) rule, err = Parse("foo=bar|bar=foo") assert.Nil(t, err, "There should be no errors but got: %s", err) - assert.IsType(t, &Any{}, rule) + expected = &Chain{op: ANY, rules: []Filter{ + &Equal{column: "foo", value: "bar"}, + &Equal{column: "bar", value: "foo"}, + }} + assert.Equal(t, expected, rule) + }) + + t.Run("ParseChain", func(t *testing.T) { + t.Parallel() + + rule, err := Parse("(foo=bar)") + assert.Nil(t, err, "There should be no errors but got: %s", err) + assert.Equal(t, &Equal{column: "foo", value: "bar"}, rule) + + rule, err = Parse("(!foo=bar)") + assert.Nil(t, err, "There should be no errors but got: %s", err) + expected := &Chain{op: NONE, rules: []Filter{&Equal{column: "foo", value: "bar"}}} + assert.Equal(t, expected, rule) + + rule, err = Parse("!(foo=bar)") + assert.Nil(t, err, "There should be no errors but got: %s", err) + expected = &Chain{op: NONE, rules: []Filter{&Equal{column: "foo", value: "bar"}}} + assert.Equal(t, expected, rule) + + rule, err = Parse("!(!foo=bar)") + assert.Nil(t, err, "There should be no errors but got: %s", err) + expected = &Chain{op: NONE, rules: []Filter{ + &Chain{op: NONE, rules: []Filter{ + &Equal{column: "foo", value: "bar"}, + }}, + }} + assert.Equal(t, expected, rule) rule, err = Parse("!(foo=bar|bar=foo)") assert.Nil(t, err, "There should be no errors but got: %s", err) - assert.IsType(t, &None{}, rule) + expected = &Chain{op: NONE, rules: []Filter{ + &Chain{op: ANY, rules: []Filter{ + &Equal{column: "foo", value: "bar"}, + &Equal{column: "bar", value: "foo"}, + }}, + }} + assert.Equal(t, expected, rule) + + rule, err = Parse("((!foo=bar)&bar!=foo)") + assert.Nil(t, err, "There should be no errors but got: %s", err) + expected = &Chain{op: ALL, rules: []Filter{ + &Chain{op: NONE, rules: []Filter{&Equal{column: "foo", value: "bar"}}}, + &UnEqual{column: "bar", value: "foo"}, + }} + assert.Equal(t, expected, rule) - rule, err = Parse("!foo") + rule, err = Parse("!foo&!bar") assert.Nil(t, err, "There should be no errors but got: %s", err) + expected = &Chain{op: ALL, rules: []Filter{ + &Chain{op: NONE, rules: []Filter{&Exists{column: "foo"}}}, + &Chain{op: NONE, rules: []Filter{&Exists{column: "bar"}}}, + }} + assert.Equal(t, expected, rule) - assert.Equal(t, &None{rules: []Filter{NewExists("foo")}}, rule) + rule, err = Parse("!(!foo|bar)") + assert.Nil(t, err, "There should be no errors but got: %s", err) + expected = &Chain{op: NONE, rules: []Filter{ + &Chain{op: ANY, rules: []Filter{ + &Chain{op: NONE, rules: []Filter{&Exists{column: "foo"}}}, + &Exists{column: "bar"}, + }}, + }} + assert.Equal(t, expected, rule) - rule, err = Parse("foo") + rule, err = Parse("!(!(foo|bar))") assert.Nil(t, err, "There should be no errors but got: %s", err) - assert.Equal(t, NewExists("foo"), rule) + expected = &Chain{op: NONE, rules: []Filter{ + &Chain{op: NONE, rules: []Filter{ + &Chain{op: ANY, rules: []Filter{ + &Exists{column: "foo"}, + &Exists{column: "bar"}}, + }, + }}, + }} + assert.Equal(t, expected, rule) - rule, err = Parse("!(foo=bar|bar=foo)&(foo=bar|bar=foo)") + rule, err = Parse("foo=bar&bar!=foo") assert.Nil(t, err, "There should be no errors but got: %s", err) + expected = &Chain{op: ALL, rules: []Filter{ + &Equal{column: "foo", value: "bar"}, + &UnEqual{column: "bar", value: "foo"}, + }} + assert.Equal(t, expected, rule) - expected := &All{rules: []Filter{ - &None{rules: []Filter{ + rule, err = Parse("!(foo=bar|bar=foo)&(foo!=bar|bar!=foo)") + assert.Nil(t, err, "There should be no errors but got: %s", err) + expected = &Chain{op: ALL, rules: []Filter{ + &Chain{op: NONE, rules: []Filter{ + &Chain{op: ANY, rules: []Filter{ + &Equal{column: "foo", value: "bar"}, + &Equal{column: "bar", value: "foo"}, + }}, + }}, + &Chain{op: ANY, rules: []Filter{ + &UnEqual{column: "foo", value: "bar"}, + &UnEqual{column: "bar", value: "foo"}, + }}, + }} + assert.Equal(t, expected, rule) + + rule, err = Parse("foo=bar&bar!=foo&john>doe|doedoe|doedoe|doedoe|doebar") @@ -187,6 +410,7 @@ func FuzzParser(f *testing.F) { f.Add("foo") f.Add("!(foo=bar|bar=foo)&(foo=bar|bar=foo)") f.Add("foo=bar") + f.Add("col%7umn") f.Add("col%3Cumnval%29ue") f.Fuzz(func(t *testing.T, expr string) { - _, err := Parse(expr) + f, err := Parse(expr) if strings.Count(expr, "(") != strings.Count(expr, ")") { assert.Error(t, err) @@ -215,13 +439,27 @@ func FuzzParser(f *testing.F) { } if strings.Contains(expr, "&") { - assertDumpContainsAny("All") + assertDumpContainsAny("op") + assertDumpContainsAny("&") } if strings.Contains(expr, "|") { - assertDumpContainsAny("Any", "None") + assertDumpContainsAny("op") + assertDumpContainsAny("|", "!") } if strings.Contains(expr, "!") { - assertDumpContainsAny("None", "UnEqual", "Unlike") + assertDumpContainsAny("!", "UnEqual", "Unlike") + } + if strings.Contains(expr, "<") { + assertDumpContainsAny("LessThan", "LessThanOrEqual") + } + if strings.Contains(expr, ">") { + assertDumpContainsAny("GreaterThan", "GreaterThanOrEqual") + } + if strings.Contains(expr, "=") { + assertDumpContainsAny("Equal", "UnEqual", "GreaterThanOrEqual", "LessThanOrEqual") + } + if strings.Contains(expr, "~") { + assertDumpContainsAny("Like", "Unlike") } } }) diff --git a/internal/object/object_test.go b/internal/object/object_test.go index ee6f250fe..1989adcec 100644 --- a/internal/object/object_test.go +++ b/internal/object/object_test.go @@ -29,8 +29,8 @@ func TestFilter(t *testing.T) { {"Host", false}, {"service", false}, {"!service", true}, - {"host=*.example.com&hostgroup/database-server", true}, - {"host=*.example.com&!hostgroup/database-server", false}, + {"host~*.example.com&hostgroup/database-server", true}, + {"host~*.example.com&!hostgroup/database-server", false}, {"!service&(country=DE&hostgroup/database-server)", true}, {"!service&!(country=AT|country=CH)", true}, {"hostgroup/Nuremberg %28Germany%29", true},