package traverse import ( "fmt" "net" "strings" "sync" "testing" "github.com/miekg/dns" ) func TestNewInfoCache(t *testing.T) { c := NewInfoCache(nil) if c.parent != nil { t.Error("root cache should have nil parent") } if c.NSCount() != 0 { t.Errorf("NSCount = %d, want 0", c.NSCount()) } if c.GlueCount() != 0 { t.Errorf("GlueCount = %d, want 0", c.GlueCount()) } } func TestInfoCacheStoreAndLookupNS(t *testing.T) { c := NewInfoCache(nil) c.StoreNS("com.", []string{"a.gtld-servers.net.", "b.gtld-servers.net."}) if c.NSCount() != 1 { t.Errorf("NSCount = %d, want 1", c.NSCount()) } names := c.LookupNS("com.") if len(names) != 2 { t.Fatalf("expected 2 nameservers, got %d", len(names)) } if names[0] != "a.gtld-servers.net." { t.Errorf("nameserver[0] = %q, want %q", names[0], "a.gtld-servers.net.") } } func TestInfoCacheNSDedup(t *testing.T) { c := NewInfoCache(nil) c.StoreNS("com.", []string{"a.gtld-servers.net.", "a.gtld-servers.net."}) names := c.LookupNS("com.") if len(names) != 1 { t.Errorf("expected 1 deduped NS, got %d", len(names)) } } func TestInfoCacheNSCaseInsensitive(t *testing.T) { c := NewInfoCache(nil) c.StoreNS("COM.", []string{"A.GTLD-SERVERS.NET."}) names := c.LookupNS("com.") if len(names) != 1 { t.Fatalf("expected 1 NS, got %d", len(names)) } if names[0] != "a.gtld-servers.net." { t.Errorf("NS = %q, want %q", names[0], "a.gtld-servers.net.") } } func TestInfoCacheNSLookupMiss(t *testing.T) { c := NewInfoCache(nil) names := c.LookupNS("org.") if names != nil { t.Errorf("expected nil for miss, got %v", names) } } func TestInfoCacheNSStoreEmpty(t *testing.T) { c := NewInfoCache(nil) c.StoreNS("com.", nil) if c.NSCount() != 0 { t.Errorf("expected 0 after empty store, got %d", c.NSCount()) } } func TestInfoCacheChainedNS(t *testing.T) { parent := NewInfoCache(nil) parent.StoreNS("com.", []string{"a.gtld-servers.net."}) child := parent.Child() if child.parent != parent { t.Error("child parent should be the parent cache") } names := child.LookupNS("com.") if len(names) != 1 { t.Fatalf("expected 1 NS from parent, got %d", len(names)) } if child.NSCount() != 0 { t.Errorf("child NSCount = %d, want 0", child.NSCount()) } } func TestInfoCacheChildOverridesParent(t *testing.T) { parent := NewInfoCache(nil) parent.StoreNS("com.", []string{"a.gtld-servers.net."}) child := parent.Child() child.StoreNS("com.", []string{"b.gtld-servers.net."}) names := child.LookupNS("com.") if len(names) != 1 { t.Fatalf("expected 1 NS, got %d", len(names)) } if names[0] != "b.gtld-servers.net." { t.Errorf("expected child's NS to override, got %q", names[0]) } } func TestInfoCacheStoreAndLookupGlue(t *testing.T) { c := NewInfoCache(nil) addrs := []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("5.6.7.8")} c.StoreGlue("ns1.example.com.", addrs) result := c.LookupGlue("ns1.example.com.") if len(result) != 2 { t.Fatalf("expected 2 glue addresses, got %d", len(result)) } } func TestInfoCacheGlueDedup(t *testing.T) { c := NewInfoCache(nil) ip := net.ParseIP("1.2.3.4") c.StoreGlue("ns1.example.com.", []net.IP{ip, ip}) result := c.LookupGlue("ns1.example.com.") if len(result) != 1 { t.Errorf("expected 1 deduped glue, got %d", len(result)) } } func TestInfoCacheChainedGlue(t *testing.T) { parent := NewInfoCache(nil) parent.StoreGlue("ns1.example.com.", []net.IP{net.ParseIP("1.2.3.4")}) child := parent.Child() result := child.LookupGlue("ns1.example.com.") if len(result) != 1 { t.Fatalf("expected 1 glue from parent, got %d", len(result)) } if child.GlueCount() != 0 { t.Errorf("child GlueCount = %d, want 0", child.GlueCount()) } } func TestInfoCacheGlueLookupMiss(t *testing.T) { c := NewInfoCache(nil) result := c.LookupGlue("nonexistent.example.com.") if result != nil { t.Errorf("expected nil for miss, got %v", result) } } func TestInfoCacheConcurrentAccess(t *testing.T) { c := NewInfoCache(nil) var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func(i int) { defer wg.Done() name := strings.ToLower(dns.Fqdn(fmt.Sprintf("ns%d.example.com.", i))) c.StoreNS("example.com.", []string{name}) c.StoreGlue(name, []net.IP{net.ParseIP(fmt.Sprintf("1.2.3.%d", i%256))}) _ = c.LookupNS("example.com.") _ = c.LookupGlue(name) }(i) } wg.Wait() } func TestInfoCacheNilParent(t *testing.T) { c := NewInfoCache(nil) if c.LookupNS("com.") != nil { t.Error("root cache should return nil for miss") } if c.LookupGlue("ns.example.com.") != nil { t.Error("root cache should return nil for glue miss") } } func TestNormalize(t *testing.T) { tests := []struct { input string want string }{ {"example.com", "example.com."}, {"Example.COM.", "example.com."}, {"EXAMPLE.COM", "example.com."}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { got := normalize(tt.input) if got != tt.want { t.Errorf("normalize(%q) = %q, want %q", tt.input, got, tt.want) } }) } }