package geo import ( "sync/atomic" "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 TestGeoMatcher_MatchGeoSiteSet(t *testing.T) { loader := &fakeGeoLoader{ geosite: map[string]*v2geo.GeoSite{ "openai": { Domain: []*v2geo.Domain{ {Type: v2geo.Domain_Plain, Value: "openai"}, }, }, "google": { Domain: []*v2geo.Domain{ {Type: v2geo.Domain_RootDomain, Value: "google.com"}, }, }, }, } g := NewGeoMatcher("", "") g.geoLoader = loader set := &SiteConditionSet{Conditions: []string{" google ", "openai", "OPENAI"}} if !g.MatchGeoSiteSet("api.openai.com", set) { t.Error("MatchGeoSiteSet should match openai") } if !g.MatchGeoSiteSet("mail.google.com", set) { t.Error("MatchGeoSiteSet should match google") } if g.MatchGeoSiteSet("example.com", set) { t.Error("MatchGeoSiteSet should not match unrelated host") } } type countingMatcher struct { calls *atomic.Uint64 match bool } func (m countingMatcher) Match(host HostInfo) bool { _ = host m.calls.Add(1) return m.match } func TestGeoMatcher_MatchGeoSite_UsesResultCache(t *testing.T) { g := NewGeoMatcher("", "") var calls atomic.Uint64 g.geoSiteMatcher["openai"] = countingMatcher{calls: &calls, match: true} if !g.MatchGeoSite("api.openai.com", "openai") { t.Fatal("expected match") } if !g.MatchGeoSite("api.openai.com", "openai") { t.Fatal("expected cached match") } if got := calls.Load(); got != 1 { t.Fatalf("matcher calls=%d want=1", got) } } func TestGeoMatcher_MatchGeoSiteSet_UsesResultCache(t *testing.T) { g := NewGeoMatcher("", "") var calls atomic.Uint64 g.geoSiteSets["openai\x1fyoutube"] = []hostMatcher{ countingMatcher{calls: &calls, match: false}, countingMatcher{calls: &calls, match: true}, } set := &SiteConditionSet{Conditions: []string{"youtube", "openai"}} if !g.MatchGeoSiteSet("www.youtube.com", set) { t.Fatal("expected match") } if !g.MatchGeoSiteSet("www.youtube.com", set) { t.Fatal("expected cached match") } if got := calls.Load(); got != 2 { t.Fatalf("matcher calls=%d want=2", got) } } func ipv4(a, b, c, d byte) []byte { return []byte{a, b, c, d} }