mirror of
https://github.com/TecharoHQ/anubis.git
synced 2025-09-17 00:33:54 -04:00

* feat: glob matching for redirect domains Signed-off-by: Xe Iaso <me@xeiaso.net> * chore: spelling Signed-off-by: Xe Iaso <me@xeiaso.net> * docs: update CHANGELOG Signed-off-by: Xe Iaso <me@xeiaso.net> --------- Signed-off-by: Xe Iaso <me@xeiaso.net>
190 lines
5.3 KiB
Go
190 lines
5.3 KiB
Go
package glob
|
|
|
|
import "testing"
|
|
|
|
func TestGlob_EqualityAndEmpty(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
pattern string
|
|
subj string
|
|
want bool
|
|
}{
|
|
{"exact match", "hello", "hello", true},
|
|
{"exact mismatch", "hello", "hell", false},
|
|
{"empty pattern and subject", "", "", true},
|
|
{"empty pattern with non-empty subject", "", "x", false},
|
|
{"pattern star matches empty", "*", "", true},
|
|
{"pattern star matches anything", "*", "anything at all", true},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := Glob(tc.pattern, tc.subj); got != tc.want {
|
|
t.Fatalf("Glob(%q,%q) = %v, want %v", tc.pattern, tc.subj, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGlob_LeadingAndTrailing(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
pattern string
|
|
subj string
|
|
want bool
|
|
}{
|
|
{"prefix match - minimal", "foo*", "foo", true},
|
|
{"prefix match - extended", "foo*", "foobar", true},
|
|
{"prefix mismatch - not at start", "foo*", "xfoo", false},
|
|
{"suffix match - minimal", "*foo", "foo", true},
|
|
{"suffix match - extended", "*foo", "xfoo", true},
|
|
{"suffix mismatch - not at end", "*foo", "foox", false},
|
|
{"contains match", "*foo*", "barfoobaz", true},
|
|
{"contains mismatch - missing needle", "*foo*", "f", false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := Glob(tc.pattern, tc.subj); got != tc.want {
|
|
t.Fatalf("Glob(%q,%q) = %v, want %v", tc.pattern, tc.subj, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGlob_MiddleAndOrder(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
pattern string
|
|
subj string
|
|
want bool
|
|
}{
|
|
{"middle wildcard basic", "f*o", "fo", true},
|
|
{"middle wildcard gap", "f*o", "fZZZo", true},
|
|
{"middle wildcard requires start f", "f*o", "xfyo", false},
|
|
{"order enforced across parts", "a*b*c*d", "axxbxxcxxd", true},
|
|
{"order mismatch fails", "a*b*c*d", "abdc", false},
|
|
{"must end with last part when no trailing *", "*foo*bar", "zzfooqqbar", true},
|
|
{"failing when trailing chars remain", "*foo*bar", "zzfooqqbarzz", false},
|
|
{"first part must start when no leading *", "foo*bar", "zzfooqqbar", false},
|
|
{"works with overlapping content", "ab*ba", "ababa", true},
|
|
{"needle not found fails", "foo*bar", "foobaz", false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := Glob(tc.pattern, tc.subj); got != tc.want {
|
|
t.Fatalf("Glob(%q,%q) = %v, want %v", tc.pattern, tc.subj, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGlob_ConsecutiveStarsAndEmptyParts(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
pattern string
|
|
subj string
|
|
want bool
|
|
}{
|
|
{"double star matches anything", "**", "", true},
|
|
{"double star matches anything non-empty", "**", "abc", true},
|
|
{"consecutive stars behave like single", "a**b", "ab", true},
|
|
{"consecutive stars with gaps", "a**b", "axxxb", true},
|
|
{"consecutive stars + trailing star", "a**b*", "axxbzzz", true},
|
|
{"consecutive stars still enforce anchors", "a**b", "xaBy", false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := Glob(tc.pattern, tc.subj); got != tc.want {
|
|
t.Fatalf("Glob(%q,%q) = %v, want %v", tc.pattern, tc.subj, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGlob_MaxPartsLimit(t *testing.T) {
|
|
// Allowed: up to 4 '*' (5 parts)
|
|
allowed := []struct {
|
|
pattern string
|
|
subj string
|
|
want bool
|
|
}{
|
|
{"a*b*c*d*e", "axxbxxcxxdxxe", true}, // 4 stars -> 5 parts
|
|
{"*a*b*c*d", "zzzaaaabbbcccddd", true},
|
|
{"a*b*c*d*e", "abcde", true},
|
|
{"a*b*c*d*e", "abxdxe", false}, // missing 'c' should fail
|
|
}
|
|
for _, tc := range allowed {
|
|
if got := Glob(tc.pattern, tc.subj); got != tc.want {
|
|
t.Fatalf("allowed pattern Glob(%q,%q) = %v, want %v", tc.pattern, tc.subj, got, tc.want)
|
|
}
|
|
}
|
|
|
|
// Disallowed: 5 '*' (6 parts) -> always false by complexity check
|
|
disallowed := []struct {
|
|
pattern string
|
|
subj string
|
|
}{
|
|
{"a*b*c*d*e*f", "aXXbYYcZZdQQeRRf"},
|
|
{"*a*b*c*d*e*", "abcdef"},
|
|
{"******", "anything"}, // 6 stars -> 7 parts
|
|
}
|
|
for _, tc := range disallowed {
|
|
if got := Glob(tc.pattern, tc.subj); got {
|
|
t.Fatalf("disallowed pattern should fail Glob(%q,%q) = %v, want false", tc.pattern, tc.subj, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGlob_CaseSensitivity(t *testing.T) {
|
|
cases := []struct {
|
|
pattern string
|
|
subj string
|
|
want bool
|
|
}{
|
|
{"FOO*", "foo", false},
|
|
{"*Bar", "bar", false},
|
|
{"Foo*Bar", "FooZZZBar", true},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := Glob(tc.pattern, tc.subj); got != tc.want {
|
|
t.Fatalf("Glob(%q,%q) = %v, want %v", tc.pattern, tc.subj, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGlob_EmptySubjectInteractions(t *testing.T) {
|
|
cases := []struct {
|
|
pattern string
|
|
subj string
|
|
want bool
|
|
}{
|
|
{"*a", "", false},
|
|
{"a*", "", false},
|
|
{"**", "", true},
|
|
{"*", "", true},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := Glob(tc.pattern, tc.subj); got != tc.want {
|
|
t.Fatalf("Glob(%q,%q) = %v, want %v", tc.pattern, tc.subj, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkGlob(b *testing.B) {
|
|
patterns := []string{
|
|
"*", "*foo*", "foo*bar", "a*b*c*d*e", "a**b*", "*needle*end",
|
|
}
|
|
subjects := []string{
|
|
"", "foo", "barfoo", "foobarbaz", "axxbxxcxxdxxe", "zzfooqqbarzz",
|
|
"lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
|
}
|
|
for _, p := range patterns {
|
|
for _, s := range subjects {
|
|
b.Run(p+"::"+s, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_ = Glob(p, s)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|