package resolver import ( "sync" "testing" ) func TestNewDNSResolver(t *testing.T) { resolver := NewDNSResolver() if resolver == nil { t.Fatal("NewDNSResolver returned nil") } if resolver.cache == nil { t.Fatal("cache map not initialized") } if len(resolver.cache) != 0 { t.Errorf("expected empty cache, got size %d", len(resolver.cache)) } } func TestIsIPAddress(t *testing.T) { tests := []struct { input string expected bool }{ {"192.168.1.1", true}, {"10.0.0.1", true}, {"255.255.255.255", true}, {"2001:db8::1", true}, {"::1", true}, {"not-an-ip", false}, {"", false}, {"256.1.1.1", false}, {"192.168.1", false}, {"server.example.com", false}, } for _, tt := range tests { result := isIPAddress(tt.input) if result != tt.expected { t.Errorf("isIPAddress(%q) = %v, want %v", tt.input, result, tt.expected) } } } func TestResolveIP_InvalidIP(t *testing.T) { resolver := NewDNSResolver() invalidInputs := []string{ "not-an-ip", "server.example.com", "", "invalid", } for _, input := range invalidInputs { hostname, ok := resolver.ResolveIP(input) if ok { t.Errorf("ResolveIP(%q) returned ok=true for invalid IP", input) } if hostname != "" { t.Errorf("ResolveIP(%q) returned hostname=%q for invalid IP", input, hostname) } } } func TestResolveIP_Localhost(t *testing.T) { resolver := NewDNSResolver() // Test localhost resolution (should work on most systems) hostname, ok := resolver.ResolveIP("127.0.0.1") // We expect either success with "localhost" or failure (depending on system DNS config) // Just verify the function doesn't panic and caches the result if ok && hostname == "" { t.Error("ResolveIP returned ok=true but empty hostname") } // Verify caching - second call should hit cache initialCacheSize := resolver.GetCacheSize() if initialCacheSize != 1 { t.Errorf("expected cache size 1 after first lookup, got %d", initialCacheSize) } // Second lookup should use cache hostname2, ok2 := resolver.ResolveIP("127.0.0.1") if hostname2 != hostname || ok2 != ok { t.Error("second lookup returned different result (cache not working)") } // Cache size should still be 1 if resolver.GetCacheSize() != 1 { t.Errorf("cache size changed on second lookup") } } func TestResolveIP_CachingFailedLookup(t *testing.T) { resolver := NewDNSResolver() // Use a private IP that likely won't resolve nonExistentIP := "192.168.255.254" // First lookup hostname1, ok1 := resolver.ResolveIP(nonExistentIP) // Verify it's cached (even if it failed) if resolver.GetCacheSize() != 1 { t.Errorf("expected cache size 1 after first lookup, got %d", resolver.GetCacheSize()) } // Second lookup should return same result from cache hostname2, ok2 := resolver.ResolveIP(nonExistentIP) if hostname1 != hostname2 { t.Errorf("hostname changed between lookups: %q vs %q", hostname1, hostname2) } if ok1 != ok2 { t.Errorf("ok changed between lookups: %v vs %v", ok1, ok2) } // Cache size should still be 1 if resolver.GetCacheSize() != 1 { t.Errorf("cache size should remain 1, got %d", resolver.GetCacheSize()) } } func TestResolveIP_MultipleIPs(t *testing.T) { resolver := NewDNSResolver() ips := []string{ "127.0.0.1", "192.168.1.1", "10.0.0.1", } // Resolve all IPs for _, ip := range ips { resolver.ResolveIP(ip) } // Verify all are cached if resolver.GetCacheSize() != len(ips) { t.Errorf("expected cache size %d, got %d", len(ips), resolver.GetCacheSize()) } // Resolve again to verify cache hits for _, ip := range ips { resolver.ResolveIP(ip) } // Cache size should not change if resolver.GetCacheSize() != len(ips) { t.Errorf("cache size changed on second pass") } } func TestResolveIP_Concurrent(t *testing.T) { resolver := NewDNSResolver() // Test concurrent access to ensure thread safety var wg sync.WaitGroup numGoroutines := 50 for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(id int) { defer wg.Done() // Each goroutine resolves a few IPs ips := []string{ "127.0.0.1", "192.168.1.1", "10.0.0.1", } for _, ip := range ips { resolver.ResolveIP(ip) } }(i) } wg.Wait() // Despite concurrent access, cache should only have unique IPs expectedSize := 3 if resolver.GetCacheSize() != expectedSize { t.Errorf("expected cache size %d after concurrent access, got %d", expectedSize, resolver.GetCacheSize()) } } func TestResolveIP_IPv6(t *testing.T) { resolver := NewDNSResolver() // Test IPv6 localhost hostname, ok := resolver.ResolveIP("::1") // Should be cached regardless of success if resolver.GetCacheSize() != 1 { t.Errorf("expected cache size 1, got %d", resolver.GetCacheSize()) } // If successful, hostname should not be empty if ok && hostname == "" { t.Error("ResolveIP returned ok=true but empty hostname for IPv6") } } func TestGetCacheSize(t *testing.T) { resolver := NewDNSResolver() if resolver.GetCacheSize() != 0 { t.Errorf("expected initial cache size 0, got %d", resolver.GetCacheSize()) } // Add entries resolver.ResolveIP("127.0.0.1") if resolver.GetCacheSize() != 1 { t.Errorf("expected cache size 1 after first entry, got %d", resolver.GetCacheSize()) } resolver.ResolveIP("192.168.1.1") if resolver.GetCacheSize() != 2 { t.Errorf("expected cache size 2 after second entry, got %d", resolver.GetCacheSize()) } // Resolving same IP shouldn't increase size resolver.ResolveIP("127.0.0.1") if resolver.GetCacheSize() != 2 { t.Errorf("expected cache size to remain 2, got %d", resolver.GetCacheSize()) } }