210 lines
5.9 KiB
Go
210 lines
5.9 KiB
Go
package domain
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewPattern_Canonical_And_Superset(t *testing.T) {
|
|
t.Run("canonical ordering and kinds", func(t *testing.T) {
|
|
p, err := NewPattern("ns", map[string]TagSpec{
|
|
"b": {Kind: MatchExact, Params: map[string]string{"y": "2", "x": "1"}},
|
|
"a": {Kind: MatchNone},
|
|
"c": {Kind: MatchAny},
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, want := p.Key(), "ns::a[]:b[x=1;y=2]:c[*]"; got != want {
|
|
t.Fatalf("got %q want %q", got, want)
|
|
}
|
|
})
|
|
|
|
t.Run("superset via flag", func(t *testing.T) {
|
|
p, err := NewPattern("ns", map[string]TagSpec{"a": {Kind: MatchNone}}, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, want := p.Key(), "ns::a[].*"; got != want {
|
|
t.Fatalf("got %q want %q", got, want)
|
|
}
|
|
})
|
|
|
|
t.Run("superset via '*' tag anywhere", func(t *testing.T) {
|
|
p, err := NewPattern("ns", map[string]TagSpec{
|
|
"*": {Kind: MatchAny}, // triggers superset; omitted from canonical
|
|
"a": {Kind: MatchNone},
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, want := p.Key(), "ns::a[].*"; got != want {
|
|
t.Fatalf("got %q want %q", got, want)
|
|
}
|
|
})
|
|
|
|
t.Run("trim and validate", func(t *testing.T) {
|
|
p, err := NewPattern(" ns ", map[string]TagSpec{
|
|
" tag ": {Kind: MatchAny},
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if p.Key() != "ns::tag[*]" {
|
|
t.Fatalf("unexpected canonical: %q", p.Key())
|
|
}
|
|
})
|
|
|
|
t.Run("reject invalid inputs", func(t *testing.T) {
|
|
_, err := NewPattern("", nil, false)
|
|
if err == nil {
|
|
t.Fatal("expected error for empty namespace")
|
|
}
|
|
_, err = NewPattern("ns", map[string]TagSpec{"": {Kind: MatchAny}}, false)
|
|
if err == nil {
|
|
t.Fatal("expected error for empty tag")
|
|
}
|
|
_, err = NewPattern("ns", map[string]TagSpec{"bad:": {Kind: MatchAny}}, false)
|
|
if err == nil {
|
|
t.Fatal("expected error for ':' in tag")
|
|
}
|
|
_, err = NewPattern("ns", map[string]TagSpec{"a": {Kind: MatchExact, Params: map[string]string{"": "1"}}}, false)
|
|
if err == nil {
|
|
t.Fatal("expected error for empty param key")
|
|
}
|
|
_, err = NewPattern("ns", map[string]TagSpec{"a": {Kind: MatchExact, Params: map[string]string{"k": "bad;val"}}}, false)
|
|
if err == nil {
|
|
t.Fatal("expected error for bad param value")
|
|
}
|
|
})
|
|
|
|
t.Run("MatchExact with empty params downgrades to []", func(t *testing.T) {
|
|
// Behavior matches current constructor: empty exact => MatchNone
|
|
p, err := NewPattern("ns", map[string]TagSpec{
|
|
"a": {Kind: MatchExact, Params: map[string]string{}},
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if p.Key() != "ns::a[]" {
|
|
t.Fatalf("unexpected canonical for empty exact: %q", p.Key())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPattern_Parse_Tokens_And_SupersetRecognition(t *testing.T) {
|
|
t.Run("accept :* token and .*", func(t *testing.T) {
|
|
ns, specs, sup, err := NewPatternFromRaw("ns::a[]:*:b[*]").Parse()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ns != "ns" || !sup {
|
|
t.Fatalf("ns=%q sup=%v", ns, sup)
|
|
}
|
|
if specs["a"].Kind != MatchNone || specs["b"].Kind != MatchAny {
|
|
t.Fatalf("unexpected specs: %#v", specs)
|
|
}
|
|
|
|
_, specs2, sup2, err := NewPatternFromRaw("ns::a[]:b[*].*").Parse()
|
|
if err != nil || !sup2 {
|
|
t.Fatalf("parse superset suffix failed: err=%v sup=%v", err, sup2)
|
|
}
|
|
if !reflect.DeepEqual(specs, specs2) {
|
|
t.Fatalf("specs mismatch between forms")
|
|
}
|
|
})
|
|
|
|
t.Run("first-wins on duplicate tags and params", func(t *testing.T) {
|
|
_, specs, sup, err := NewPatternFromRaw("ns::t[x=1;y=2]:t[*]:u[k=1;k=2]").Parse()
|
|
if err != nil || sup {
|
|
t.Fatalf("err=%v sup=%v", err, sup)
|
|
}
|
|
if specs["t"].Kind != MatchExact || specs["t"].Params["x"] != "1" {
|
|
t.Fatalf("first tag should win: %#v", specs["t"])
|
|
}
|
|
if specs["u"].Params["k"] != "1" {
|
|
t.Fatalf("first param key should win: %#v", specs["u"])
|
|
}
|
|
})
|
|
|
|
t.Run("reject malformed", func(t *testing.T) {
|
|
bads := []string{
|
|
"", "no_ns", "ns:onecolon", "::missingns::tag[]",
|
|
"ns::tag[", "ns::tag]", "ns::[]", "ns::tag[]junk",
|
|
"ns::a[=1]", "ns::a[x=]", "ns::a[ x=1 ]",
|
|
}
|
|
for _, s := range bads {
|
|
_, _, _, err := NewPatternFromRaw(s).Parse()
|
|
if err == nil {
|
|
t.Fatalf("expected parse error for %q", s)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPattern_Match_Matrix(t *testing.T) {
|
|
makeID := func(key string) Identifier { return NewIdentifierFromRaw(key) }
|
|
|
|
t.Run("namespace mismatch", func(t *testing.T) {
|
|
p := NewPatternFromRaw("ns::a[]")
|
|
if p.Match(makeID("other::a[]")) {
|
|
t.Fatal("should not match different namespace")
|
|
}
|
|
})
|
|
|
|
t.Run("MatchAny accepts empty and nonempty", func(t *testing.T) {
|
|
p := NewPatternFromRaw("ns::a[*]")
|
|
if !p.Match(makeID("ns::a[]")) || !p.Match(makeID("ns::a[x=1]")) {
|
|
t.Fatal("MatchAny should accept both")
|
|
}
|
|
})
|
|
|
|
t.Run("MatchNone requires empty", func(t *testing.T) {
|
|
p := NewPatternFromRaw("ns::a[]")
|
|
if !p.Match(makeID("ns::a[]")) {
|
|
t.Fatal("empty should match")
|
|
}
|
|
if p.Match(makeID("ns::a[x=1]")) {
|
|
t.Fatal("nonempty should not match MatchNone")
|
|
}
|
|
})
|
|
|
|
t.Run("MatchExact equals, order independent", func(t *testing.T) {
|
|
p := NewPatternFromRaw("ns::a[x=1;y=2]")
|
|
if !p.Match(makeID("ns::a[y=2;x=1]")) {
|
|
t.Fatal("param order should not matter")
|
|
}
|
|
if p.Match(makeID("ns::a[x=1]")) {
|
|
t.Fatal("missing param should fail")
|
|
}
|
|
if p.Match(makeID("ns::a[x=1;y=2;z=3]")) {
|
|
t.Fatal("extra param should fail exact")
|
|
}
|
|
if p.Match(makeID("ns::a[x=9;y=2]")) {
|
|
t.Fatal("different value should fail")
|
|
}
|
|
})
|
|
|
|
t.Run("exact-set vs superset", func(t *testing.T) {
|
|
exact := NewPatternFromRaw("ns::a[]:b[*]")
|
|
super := NewPatternFromRaw("ns::a[]:b[*].*")
|
|
|
|
if !exact.Match(makeID("ns::a[].b[x=1]")) {
|
|
t.Fatal("exact should match same set")
|
|
}
|
|
if exact.Match(makeID("ns::a[].b[x=1].c[]")) {
|
|
t.Fatal("exact should not allow extra tags")
|
|
}
|
|
if !super.Match(makeID("ns::a[].b[x=1].c[]")) {
|
|
t.Fatal("superset should allow extra tags")
|
|
}
|
|
})
|
|
|
|
t.Run("all pattern tags must exist", func(t *testing.T) {
|
|
p := NewPatternFromRaw("ns::a[]:b[*]")
|
|
if p.Match(makeID("ns::a[]")) {
|
|
t.Fatal("missing b should fail")
|
|
}
|
|
})
|
|
}
|