package rules import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // testTree = Tree + CheckErr type testTree struct { Matcher string Not bool Value []string RuleLeft *testTree RuleRight *testTree // CheckErr allow knowing if a Tree has a rule error. CheckErr bool } func TestRuleMatch(t *testing.T) { matchers := []string{"m"} testCases := []struct { desc string rule string tree testTree matchers []string values []string expectParseErr bool }{ { desc: "No rule", rule: "", expectParseErr: true, }, { desc: "No matcher in rule", rule: "m", expectParseErr: true, }, { desc: "No value in rule", rule: "m()", tree: testTree{ Matcher: "m", Value: []string{}, CheckErr: true, }, }, { desc: "Empty value in rule", rule: "m(``)", tree: testTree{ Matcher: "m", Value: []string{""}, CheckErr: true, }, matchers: []string{"m"}, values: []string{""}, }, { desc: "One value in rule with and", rule: "m(`1`) &&", expectParseErr: true, }, { desc: "One value in rule with or", rule: "m(`1`) ||", expectParseErr: true, }, { desc: "One value in rule with missing back tick", rule: "m(`1)", expectParseErr: true, }, { desc: "One value in rule with missing opening parenthesis", rule: "m(`1`))", expectParseErr: true, }, { desc: "One value in rule with missing closing parenthesis", rule: "(m(`1`)", expectParseErr: true, }, { desc: "One value in rule", rule: "m(`1`)", tree: testTree{ Matcher: "m", Value: []string{"1"}, }, matchers: []string{"m"}, values: []string{"1"}, }, { desc: "One value in rule with superfluous parenthesis", rule: "(m(`1`))", tree: testTree{ Matcher: "m", Value: []string{"1"}, }, matchers: []string{"m"}, values: []string{"1"}, }, { desc: "Rule with CAPS matcher", rule: "M(`1`)", tree: testTree{ Matcher: "m", Value: []string{"1"}, }, matchers: []string{"m"}, values: []string{"1"}, }, { desc: "Invalid matcher in rule", rule: "w(`1`)", expectParseErr: true, }, { desc: "Invalid matchers", rule: "m(`1`)", tree: testTree{ Matcher: "m", Value: []string{"1"}, }, matchers: []string{"not-m"}, }, { desc: "Two value in rule", rule: "m(`1`, `2`)", tree: testTree{ Matcher: "m", Value: []string{"1", "2"}, }, matchers: []string{"m"}, values: []string{"1", "2"}, }, { desc: "Not one value in rule", rule: "!m(`1`)", tree: testTree{ Matcher: "m", Not: true, Value: []string{"1"}, }, matchers: []string{"m"}, values: []string{"1"}, }, { desc: "Two value in rule with and", rule: "m(`1`) && m(`2`)", tree: testTree{ Matcher: "and", CheckErr: true, RuleLeft: &testTree{ Matcher: "m", Value: []string{"1"}, }, RuleRight: &testTree{ Matcher: "m", Value: []string{"2"}, }, }, matchers: []string{"m"}, values: []string{"1", "2"}, }, { desc: "Two value in rule with or", rule: "m(`1`) || m(`2`)", tree: testTree{ Matcher: "or", CheckErr: true, RuleLeft: &testTree{ Matcher: "m", Value: []string{"1"}, }, RuleRight: &testTree{ Matcher: "m", Value: []string{"2"}, }, }, matchers: []string{"m"}, values: []string{"1", "2"}, }, { desc: "Two value in rule with and negated", rule: "!(m(`1`) && m(`2`))", tree: testTree{ Matcher: "or", CheckErr: true, RuleLeft: &testTree{ Matcher: "m", Not: true, Value: []string{"1"}, }, RuleRight: &testTree{ Matcher: "m", Not: true, Value: []string{"2"}, }, }, matchers: []string{"m"}, values: []string{"1", "2"}, }, { desc: "Two value in rule with or negated", rule: "!(m(`1`) || m(`2`))", tree: testTree{ Matcher: "and", CheckErr: true, RuleLeft: &testTree{ Matcher: "m", Not: true, Value: []string{"1"}, }, RuleRight: &testTree{ Matcher: "m", Not: true, Value: []string{"2"}, }, }, matchers: []string{"m"}, values: []string{"1", "2"}, }, { desc: "No value in rule", rule: "m(`1`) && m()", tree: testTree{ Matcher: "and", CheckErr: true, RuleLeft: &testTree{ Matcher: "m", Value: []string{"1"}, }, RuleRight: &testTree{ Matcher: "m", Value: []string{}, CheckErr: true, }, }, matchers: []string{"m"}, values: []string{"1"}, }, } parser, err := NewParser(matchers) require.NoError(t, err) for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { t.Parallel() parse, err := parser.Parse(test.rule) if test.expectParseErr { require.Error(t, err) return } require.NoError(t, err) treeBuilder, ok := parse.(TreeBuilder) require.True(t, ok) tree := treeBuilder() checkEquivalence(t, &test.tree, tree) assert.Equal(t, test.values, tree.ParseMatchers(test.matchers)) }) } } func checkEquivalence(t *testing.T, expected *testTree, actual *Tree) { t.Helper() if actual == nil { return } if actual.RuleLeft != nil { checkEquivalence(t, expected.RuleLeft, actual.RuleLeft) } if actual.RuleRight != nil { checkEquivalence(t, expected.RuleRight, actual.RuleRight) } assert.Equal(t, expected.Matcher, actual.Matcher) assert.Equal(t, expected.Not, actual.Not) assert.Equal(t, expected.Value, actual.Value) t.Logf("%+v -> %v", actual, CheckRule(actual)) if expected.CheckErr { assert.Error(t, CheckRule(actual)) } else { assert.NoError(t, CheckRule(actual)) } }