// SPDX-License-Identifier: GPL-3.0-or-later package model import ( "strings" "testing" "git.labunix.xyz/krino/internal/sexp" ) // parseOne reads one condition from text, for the tests below. func parseOne(t *testing.T, text string) *Cond { t.Helper() nodes, err := sexp.Parse("test", []byte(text)) if err != nil || len(nodes) != 1 { t.Fatalf("%q does not parse: %v", text, err) } return ParseCond(nodes[0]) } // TestCondRoundTrip: a condition read into the tree and written back is the // same condition, however deep it nests. func TestCondRoundTrip(t *testing.T) { for _, text := range []string{ `(type pdf)`, `(name "^faktura" "^fv")`, `(duplicate)`, `(and (type pdf) (content "invoice"))`, `(or (and (type doc) (age > 90d)) (not (duplicate "~/docs")))`, `(not (and (name "a;b") (size > 10M)))`, } { c := parseOne(t, text) if got := c.Text(); got != text { t.Errorf("round trip of %q gave %q", text, got) } if err := c.Parse(); err != nil { t.Errorf("%q does not parse after the round trip: %v", text, err) } } } // TestCondTree: an operator holds its conditions, and the tree says how // deep each one sits. func TestCondTree(t *testing.T) { c := parseOne(t, `(and (type pdf) (or (name "a") (name "b")))`) if c.Kind != "and" || len(c.Children) != 2 { t.Fatalf("tree = %+v", c) } var depths []string c.Walk(0, nil, func(node, parent *Cond, depth int) { depths = append(depths, strings.Repeat(" ", depth)+node.Kind) }) want := []string{"and", " type", " or", " name", " name"} if strings.Join(depths, "|") != strings.Join(want, "|") { t.Errorf("walk = %v, want %v", depths, want) } } // TestCondAddAndRemove: conditions go under an operator and come out again; // a test holds none. func TestCondAddAndRemove(t *testing.T) { c := parseOne(t, `(and (type pdf))`) added := c.Add("content") if added == nil || len(c.Children) != 2 { t.Fatalf("after adding: %+v", c) } added.Args = `"invoice"` if got := c.Text(); got != `(and (type pdf) (content "invoice"))` { t.Errorf("text = %q", got) } if !c.Remove(added) || len(c.Children) != 1 { t.Errorf("removing left %+v", c) } if c.Children[0].Add("name") != nil { t.Error("a test took a condition under it") } // Removing something that is not there changes nothing. if c.Remove(&Cond{Kind: "name"}) { t.Error("removed a condition that is not in the tree") } } // TestCondEmptyOperator: an operator with nothing under it writes nothing // rather than "(and)", which krino would refuse, and says so when asked. func TestCondEmptyOperator(t *testing.T) { c := &Cond{Kind: "and"} if got := c.Text(); got != "" { t.Errorf("text = %q, want nothing", got) } if err := c.Parse(); err == nil { t.Error("an empty operator passed the check") } }