test: improve coverage across package

This commit is contained in:
2026-05-01 14:09:10 +05:30
parent e1c68ec7d0
commit e3f1f5046a
18 changed files with 2652 additions and 6 deletions

View File

@@ -0,0 +1,98 @@
package builtins
import (
"net"
"testing"
)
func TestCompileCIDR(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
wantStr string
}{
{"valid ipv4", "192.168.0.0/24", false, "192.168.0.0/24"},
{"valid ipv6", "2001:db8::/32", false, "2001:db8::/32"},
{"valid host ipv4", "10.0.0.1/32", false, "10.0.0.1/32"},
{"valid host ipv6", "::1/128", false, "::1/128"},
{"invalid no mask", "192.168.0.0", true, ""},
{"invalid bad ip", "not-an-ip/24", true, ""},
{"invalid empty", "", true, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CompileCIDR(tt.input)
if tt.wantErr {
if err == nil {
t.Errorf("CompileCIDR(%q) expected error, got nil", tt.input)
}
return
}
if err != nil {
t.Fatalf("CompileCIDR(%q) unexpected error: %v", tt.input, err)
}
if got.String() != tt.wantStr {
t.Errorf("CompileCIDR(%q) = %q, want %q", tt.input, got.String(), tt.wantStr)
}
})
}
}
func TestMatchCIDR(t *testing.T) {
cidr := mustCompileCIDR(t, "192.168.0.0/24")
tests := []struct {
name string
ip string
want bool
}{
{"inside", "192.168.0.1", true},
{"boundary low", "192.168.0.0", true},
{"boundary high", "192.168.0.255", true},
{"outside", "192.168.1.1", false},
{"different network", "10.0.0.1", false},
{"invalid ip", "not-an-ip", false},
{"empty", "", false},
{"ipv6 in ipv4", "::1", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := MatchCIDR(tt.ip, cidr)
if got != tt.want {
t.Errorf("MatchCIDR(%q, %q) = %v, want %v", tt.ip, cidr, got, tt.want)
}
})
}
}
func TestMatchCIDR_IPv6(t *testing.T) {
cidr := mustCompileCIDR(t, "2001:db8::/32")
inside := "2001:db8::1"
if !MatchCIDR(inside, cidr) {
t.Errorf("MatchCIDR(%q) should be true", inside)
}
outside := "2001:db9::1"
if MatchCIDR(outside, cidr) {
t.Errorf("MatchCIDR(%q) should be false", outside)
}
}
func TestMatchCIDR_NullResult(t *testing.T) {
if MatchCIDR("10.0.0.1", &net.IPNet{}) {
t.Error("MatchCIDR with empty IPNet should return false")
}
}
func mustCompileCIDR(t *testing.T, cidr string) *net.IPNet {
t.Helper()
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
t.Fatalf("failed to parse CIDR %q: %v", cidr, err)
}
return ipNet
}

View File

@@ -0,0 +1,115 @@
package geo
import (
"testing"
"git.difuse.io/Difuse/Mellaris/ruleset/builtins/geo/v2geo"
)
type fakeGeoLoader struct {
geoip map[string]*v2geo.GeoIP
geosite map[string]*v2geo.GeoSite
}
func (l *fakeGeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) {
return l.geoip, nil
}
func (l *fakeGeoLoader) LoadGeoSite() (map[string]*v2geo.GeoSite, error) {
return l.geosite, nil
}
func TestGeoMatcher_MatchGeoIp_Cached(t *testing.T) {
loader := &fakeGeoLoader{
geoip: map[string]*v2geo.GeoIP{
"us": {
Cidr: []*v2geo.CIDR{
{Ip: ipv4(8, 8, 8, 0), Prefix: 24},
},
},
},
}
g := NewGeoMatcher("", "")
g.geoLoader = loader
if !g.MatchGeoIp("8.8.8.8", "US") {
t.Error("MatchGeoIp should match 8.8.8.8 in US range")
}
if g.MatchGeoIp("9.9.9.9", "US") {
t.Error("MatchGeoIp should not match 9.9.9.9 in US range")
}
}
func TestGeoMatcher_MatchGeoIp_EmptyCondition(t *testing.T) {
g := NewGeoMatcher("", "")
if g.MatchGeoIp("1.2.3.4", "") {
t.Error("MatchGeoIp with empty condition should return false")
}
}
func TestGeoMatcher_MatchGeoIp_InvalidIP(t *testing.T) {
g := NewGeoMatcher("", "")
if g.MatchGeoIp("not-an-ip", "us") {
t.Error("MatchGeoIp with invalid IP should return false")
}
}
func TestGeoMatcher_MatchGeoIp_MissingCountry(t *testing.T) {
loader := &fakeGeoLoader{
geoip: map[string]*v2geo.GeoIP{},
}
g := NewGeoMatcher("", "")
g.geoLoader = loader
if g.MatchGeoIp("8.8.8.8", "us") {
t.Error("MatchGeoIp for missing country should return false")
}
}
func TestGeoMatcher_MatchGeoSite(t *testing.T) {
loader := &fakeGeoLoader{
geosite: map[string]*v2geo.GeoSite{
"openai": {
Domain: []*v2geo.Domain{
{Type: v2geo.Domain_Plain, Value: "openai"},
{Type: v2geo.Domain_Full, Value: "chatgpt.com"},
},
},
},
}
g := NewGeoMatcher("", "")
g.geoLoader = loader
if !g.MatchGeoSite("api.openai.com", "openai") {
t.Error("MatchGeoSite should match via plain domain")
}
if !g.MatchGeoSite("chatgpt.com", "openai") {
t.Error("MatchGeoSite should match via full domain")
}
if g.MatchGeoSite("google.com", "openai") {
t.Error("MatchGeoSite should not match unrelated host")
}
}
func TestGeoMatcher_MatchGeoSite_EmptyCondition(t *testing.T) {
g := NewGeoMatcher("", "")
if g.MatchGeoSite("test.com", "") {
t.Error("MatchGeoSite with empty condition should return false")
}
}
func TestGeoMatcher_MatchGeoSite_MissingSite(t *testing.T) {
loader := &fakeGeoLoader{
geosite: map[string]*v2geo.GeoSite{},
}
g := NewGeoMatcher("", "")
g.geoLoader = loader
if g.MatchGeoSite("test.com", "nonexistent") {
t.Error("MatchGeoSite for missing site should return false")
}
}
func ipv4(a, b, c, d byte) []byte {
return []byte{a, b, c, d}
}

Binary file not shown.

View File

@@ -0,0 +1,324 @@
package geo
import (
"net"
"testing"
"git.difuse.io/Difuse/Mellaris/ruleset/builtins/geo/v2geo"
)
func TestParseGeoSiteName(t *testing.T) {
tests := []struct {
input string
wantBase string
wantAttrs []string
}{
{"google", "google", nil},
{"google@ads", "google", []string{"ads"}},
{"google@ads@news", "google", []string{"ads", "news"}},
{" google ", "google", nil},
{" google @ ads ", "google", []string{"ads"}},
{"openai@ ads @ news ", "openai", []string{"ads", "news"}},
{"@onlyattrs", "", []string{"onlyattrs"}},
{"", "", nil},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
base, attrs := parseGeoSiteName(tt.input)
if base != tt.wantBase {
t.Errorf("parseGeoSiteName(%q) base = %q, want %q", tt.input, base, tt.wantBase)
}
if len(attrs) != len(tt.wantAttrs) {
t.Fatalf("parseGeoSiteName(%q) attrs len = %d, want %d", tt.input, len(attrs), len(tt.wantAttrs))
}
for i, attr := range attrs {
if attr != tt.wantAttrs[i] {
t.Errorf("parseGeoSiteName(%q) attrs[%d] = %q, want %q", tt.input, i, attr, tt.wantAttrs[i])
}
}
})
}
}
func TestHostInfo_String(t *testing.T) {
h := HostInfo{
Name: "example.com",
IPv4: net.ParseIP("1.2.3.4"),
IPv6: net.ParseIP("::1"),
}
want := "example.com|1.2.3.4|::1"
if got := h.String(); got != want {
t.Errorf("HostInfo.String() = %q, want %q", got, want)
}
}
func TestHostInfo_String_Partial(t *testing.T) {
h := HostInfo{
Name: "test.com",
IPv4: net.ParseIP("10.0.0.1"),
}
want := "test.com|10.0.0.1|<nil>"
if got := h.String(); got != want {
t.Errorf("HostInfo.String() = %q, want %q", got, want)
}
}
func TestGeoipMatcher_Match(t *testing.T) {
_, n4, _ := net.ParseCIDR("10.0.0.0/8")
_, n4_2, _ := net.ParseCIDR("192.168.0.0/16")
m := &geoipMatcher{
N4: []*net.IPNet{n4, n4_2},
}
tests := []struct {
name string
host HostInfo
want bool
}{
{"ipv4 match", HostInfo{IPv4: net.ParseIP("10.1.2.3")}, true},
{"ipv4 no match", HostInfo{IPv4: net.ParseIP("172.16.0.1")}, false},
{"ipv4 match second net", HostInfo{IPv4: net.ParseIP("192.168.1.1")}, true},
{"no ip", HostInfo{}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := m.Match(tt.host); got != tt.want {
t.Errorf("Match() = %v, want %v", got, tt.want)
}
})
}
}
func TestGeoipMatcher_Match_Inverse(t *testing.T) {
_, n4, _ := net.ParseCIDR("10.0.0.0/8")
m := &geoipMatcher{
N4: []*net.IPNet{n4},
Inverse: true,
}
if m.Match(HostInfo{IPv4: net.ParseIP("10.1.2.3")}) {
t.Error("Inverse: inside range should return false")
}
if !m.Match(HostInfo{IPv4: net.ParseIP("172.16.0.1")}) {
t.Error("Inverse: outside range should return true")
}
if !m.Match(HostInfo{}) {
t.Error("Inverse: no IP should return true")
}
}
func TestGeoipMatcher_Match_IPv6(t *testing.T) {
_, n6, _ := net.ParseCIDR("2001:db8::/32")
m := &geoipMatcher{
N6: []*net.IPNet{n6},
}
if !m.Match(HostInfo{IPv6: net.ParseIP("2001:db8::1")}) {
t.Error("IPv6 match failed")
}
if m.Match(HostInfo{IPv6: net.ParseIP("2001:db9::1")}) {
t.Error("IPv6 should not match")
}
}
func TestGeositeMatcher_matchDomain_Plain(t *testing.T) {
m := &geositeMatcher{}
d := geositeDomain{
Type: geositeDomainPlain,
Value: "openai",
}
if !m.matchDomain(d, HostInfo{Name: "api.openai.com"}) {
t.Error("plain domain should match via substring")
}
if m.matchDomain(d, HostInfo{Name: "google.com"}) {
t.Error("plain domain should not match unrelated host")
}
}
func TestGeositeMatcher_matchDomain_Full(t *testing.T) {
m := &geositeMatcher{}
d := geositeDomain{
Type: geositeDomainFull,
Value: "example.com",
}
if !m.matchDomain(d, HostInfo{Name: "example.com"}) {
t.Error("full domain should match exact")
}
if m.matchDomain(d, HostInfo{Name: "www.example.com"}) {
t.Error("full domain should not match subdomain")
}
}
func TestGeositeMatcher_matchDomain_Root(t *testing.T) {
m := &geositeMatcher{}
d := geositeDomain{
Type: geositeDomainRoot,
Value: "example.com",
}
if !m.matchDomain(d, HostInfo{Name: "example.com"}) {
t.Error("root domain should match exact")
}
if !m.matchDomain(d, HostInfo{Name: "www.example.com"}) {
t.Error("root domain should match subdomain")
}
if m.matchDomain(d, HostInfo{Name: "www.example.com.au"}) {
t.Error("root domain should not match unrelated suffix")
}
}
func TestGeositeMatcher_matchDomain_Attrs(t *testing.T) {
m := &geositeMatcher{Attrs: []string{"ads"}}
d := geositeDomain{
Type: geositeDomainPlain,
Value: "google",
Attrs: map[string]bool{"ads": true},
}
if !m.matchDomain(d, HostInfo{Name: "google.com"}) {
t.Error("should match when domain has required attr")
}
dNoAttrs := geositeDomain{
Type: geositeDomainPlain,
Value: "google",
Attrs: map[string]bool{},
}
if m.matchDomain(dNoAttrs, HostInfo{Name: "google.com"}) {
t.Error("should not match when domain lacks required attr")
}
dOtherAttrs := geositeDomain{
Type: geositeDomainPlain,
Value: "google",
Attrs: map[string]bool{"news": true},
}
if m.matchDomain(dOtherAttrs, HostInfo{Name: "google.com"}) {
t.Error("should not match when domain has wrong attr")
}
}
func TestGeositeMatcher_Match(t *testing.T) {
m := &geositeMatcher{
Domains: []geositeDomain{
{Type: geositeDomainFull, Value: "exact.com"},
{Type: geositeDomainPlain, Value: "partial"},
},
}
if !m.Match(HostInfo{Name: "exact.com"}) {
t.Error("should match full domain")
}
if !m.Match(HostInfo{Name: "www.partial.net"}) {
t.Error("should match partial domain")
}
if m.Match(HostInfo{Name: "other.net"}) {
t.Error("should not match unrelated host")
}
}
func TestDomainAttributeToMap(t *testing.T) {
attrs := []*v2geo.Domain_Attribute{
{Key: "ads"},
{Key: "news"},
}
got := domainAttributeToMap(attrs)
if len(got) != 2 || !got["ads"] || !got["news"] {
t.Errorf("domainAttributeToMap = %v, want {ads:true, news:true}", got)
}
got2 := domainAttributeToMap(nil)
if len(got2) != 0 {
t.Errorf("domainAttributeToMap(nil) = %v, want empty map", got2)
}
}
func TestNewGeoIPMatcher(t *testing.T) {
list := &v2geo.GeoIP{
Cidr: []*v2geo.CIDR{
{Ip: net.IPv4(10, 0, 0, 0).To4(), Prefix: 8},
{Ip: net.IPv4(192, 168, 0, 0).To4(), Prefix: 16},
},
InverseMatch: false,
}
m, err := newGeoIPMatcher(list)
if err != nil {
t.Fatalf("newGeoIPMatcher error: %v", err)
}
if len(m.N4) != 2 {
t.Errorf("expected 2 IPv4 nets, got %d", len(m.N4))
}
if m.Inverse {
t.Error("Inverse should be false")
}
// Verify sorted order: 10.0.0.0/8 < 192.168.0.0/16
if m.N4[0].IP.String() != "10.0.0.0" {
t.Errorf("N4[0] = %s, want 10.0.0.0", m.N4[0].IP)
}
if m.N4[1].IP.String() != "192.168.0.0" {
t.Errorf("N4[1] = %s, want 192.168.0.0", m.N4[1].IP)
}
}
func TestNewGeoIPMatcher_IPv6(t *testing.T) {
list := &v2geo.GeoIP{
Cidr: []*v2geo.CIDR{
{Ip: net.ParseIP("2001:db8::"), Prefix: 32},
},
}
m, err := newGeoIPMatcher(list)
if err != nil {
t.Fatalf("newGeoIPMatcher error: %v", err)
}
if len(m.N6) != 1 {
t.Errorf("expected 1 IPv6 net, got %d", len(m.N6))
}
}
func TestNewGeoIPMatcher_InvalidIPLength(t *testing.T) {
list := &v2geo.GeoIP{
Cidr: []*v2geo.CIDR{
{Ip: []byte{1, 2, 3}, Prefix: 24},
},
}
_, err := newGeoIPMatcher(list)
if err == nil {
t.Error("expected error for invalid IP length")
}
}
func TestNewGeositeMatcher(t *testing.T) {
list := &v2geo.GeoSite{
Domain: []*v2geo.Domain{
{Type: v2geo.Domain_Plain, Value: "google"},
{Type: v2geo.Domain_Full, Value: "exact.com"},
},
}
m, err := newGeositeMatcher(list, nil)
if err != nil {
t.Fatalf("newGeositeMatcher error: %v", err)
}
if len(m.Domains) != 2 {
t.Errorf("expected 2 domains, got %d", len(m.Domains))
}
}
func TestNewGeositeMatcher_WithAttrs(t *testing.T) {
list := &v2geo.GeoSite{
Domain: []*v2geo.Domain{
{
Type: v2geo.Domain_RootDomain,
Value: "google.com",
Attribute: []*v2geo.Domain_Attribute{
{Key: "ads"},
},
},
},
}
m, err := newGeositeMatcher(list, []string{"ads"})
if err != nil {
t.Fatalf("newGeositeMatcher error: %v", err)
}
if !m.Match(HostInfo{Name: "www.google.com"}) {
t.Error("should match with root domain and attr")
}
}