package data import ( "errors" "net" "os" "path/filepath" "sync" "sync/atomic" "testing" ) // nested looks up a nested map value by successive string keys, returning "" if absent. func nested(m map[string]any, keys ...string) string { var cur any = m for _, k := range keys { mm, ok := cur.(map[string]any) if !ok { return "" } cur = mm[k] } s, _ := cur.(string) return s } // parseIP is a test helper that panics on invalid input (used only with literals). func parseIP(s string) net.IP { ip := net.ParseIP(s) if ip == nil { panic("invalid ip in test: " + s) } return ip } // The package's data helpers use relative paths ("data/..."), so tests must // run with the working directory at the repository root. TestMain moves there // once for the whole package. func TestMain(m *testing.M) { if root, err := filepath.Abs("../.."); err == nil { _ = os.Chdir(root) } os.Exit(m.Run()) } // freshDB returns a loaded IpDb, skipping the test when no mmdb is present. func freshDB(t *testing.T) *IpDb { t.Helper() db := Ins() if !db.IsLoaded() { t.Skip("no mmdb present, skipping test that needs real data") } return db } func TestQueryValidIP(t *testing.T) { db := freshDB(t) data, netRec, err := db.Query("8.8.8.8") if err != nil { t.Fatalf("Query(8.8.8.8) unexpected error: %v", err) } if data == nil { t.Fatal("Query(8.8.8.8) returned nil data") } // 8.8.8.8 is Google (AS15169), US. if got := nested(data, "country", "iso_code"); got != "US" { t.Errorf("country.iso_code = %v, want US", got) } if netRec == nil { t.Error("expected a non-nil network record") } else if !netRec.Contains(parseIP("8.8.8.8")) { t.Errorf("network %s should contain 8.8.8.8", netRec) } } func TestQueryInvalidIP(t *testing.T) { db := freshDB(t) if _, _, err := db.Query("not-an-ip"); err == nil { t.Fatal("expected error for invalid IP, got nil") } } func TestQueryNotFound(t *testing.T) { db := freshDB(t) // 240.0.0.0/4 is reserved/unallocated and should not be in a public GeoIP DB. _, _, err := db.Query("240.0.0.1") if err == nil { t.Fatal("expected not-found error, got nil") } if !errors.Is(err, ErrNotFound) { t.Errorf("expected ErrNotFound sentinel, got %v", err) } } // TestQueryReloadConcurrent is a regression test for the data race between // Query (reads d.r) and Reload (reassigns d.r / closes the old reader). It // must pass under `go test -race`. func TestQueryReloadConcurrent(t *testing.T) { db := freshDB(t) var reloadOK, queries int64 var wg sync.WaitGroup for w := 0; w < 8; w++ { wg.Add(1) go func() { defer wg.Done() for i := 0; i < 5000; i++ { _, _, _ = db.Query("8.8.8.8") atomic.AddInt64(&queries, 1) } }() } for r := 0; r < 4; r++ { wg.Add(1) go func() { defer wg.Done() for i := 0; i < 30; i++ { if db.Reload() == nil { atomic.AddInt64(&reloadOK, 1) } } }() } wg.Wait() if reloadOK == 0 { t.Fatalf("expected reloads to succeed, got reloads=%d queries=%d", reloadOK, queries) } }