Files
ExploreDNS/internal/traverse/referral_test.go
T
Garyandmultica-agent 2835ee86cf
CI / test (pull_request) Failing after 2m40s
feat: implement Phase 2.3 DNS traversal engine
Add core traversal engine with iterative resolution from root servers:
- referral.go: Referral struct with name, type, bailiwick, addresses, state
- cache.go: Chained InfoCache with parent inheritance for NS and glue records
- stack.go: LIFO stack with configurable max depth enforcement
- response.go: Response classifier (referral, answer, CNAME follow, NODATA,
  NXDOMAIN, SERVFAIL) with child referral generation and probability splitting
- traverser.go: Traverser orchestrates full traversal from root to leaf,
  following all referral branches, handling CNAME chains, and respecting
  max depth limits

Add IterativeQuery/IterativeQueryWithExchange to dns package (RD=false queries).

57 new tests covering all acceptance criteria: traversal from root to leaf,
comprehensive branch following, max depth enforcement, probability distribution,
chained cache behavior, CNAME following, NXDOMAIN/SERVFAIL handling, context
cancellation, and concurrent access.

Co-authored-by: multica-agent <github@multica.ai>
2026-06-06 13:23:01 +10:00

112 lines
2.6 KiB
Go

package traverse
import (
"net"
"testing"
"github.com/hits/ExploreDNS/internal/dns"
)
const TypeA = dns.TypeA
type State = ResolutionState
func TestNewReferral(t *testing.T) {
ref := NewReferral("example.com", TypeA, ".", 0, 1.0, nil)
if ref.Name != "example.com." {
t.Errorf("Name = %q, want %q", ref.Name, "example.com.")
}
if ref.Qtype != TypeA {
t.Errorf("Qtype = %d, want %d", ref.Qtype, TypeA)
}
if ref.Qclass != 1 {
t.Errorf("Qclass = %d, want 1", ref.Qclass)
}
if ref.State != StateUnresolved {
t.Errorf("State = %d, want %d", ref.State, StateUnresolved)
}
if ref.Depth != 0 {
t.Errorf("Depth = %d, want 0", ref.Depth)
}
if ref.Prob != 1.0 {
t.Errorf("Prob = %f, want 1.0", ref.Prob)
}
if ref.Parent != nil {
t.Error("Parent should be nil")
}
}
func TestReferralInBailiwick(t *testing.T) {
tests := []struct {
name string
bailiwick string
testName string
want bool
}{
{"root bailiwick accepts all", ".", "example.com.", true},
{"empty bailiwick accepts all", "", "example.com.", true},
{"subdomain in bailiwick", "com.", "example.com.", true},
{"deeper subdomain", "com.", "www.example.com.", true},
{"not in bailiwick", "org.", "example.com.", false},
{"same zone", "example.com.", "example.com.", true},
{"sibling zone", "example.com.", "other.com.", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref := &Referral{Bailiwick: tt.bailiwick}
if got := ref.InBailiwick(tt.testName); got != tt.want {
t.Errorf("InBailiwick(%q) = %v, want %v", tt.testName, got, tt.want)
}
})
}
}
func TestReferralHasAddresses(t *testing.T) {
ref := &Referral{}
if ref.HasAddresses() {
t.Error("empty referral should not have addresses")
}
ref.Addresses = []net.IP{net.ParseIP("1.2.3.4")}
if !ref.HasAddresses() {
t.Error("referral with address should have addresses")
}
}
func TestReferralSetAddresses(t *testing.T) {
ref := &Referral{}
ref.SetAddresses([]net.IP{net.ParseIP("1.2.3.4")})
if ref.State != StateResolved {
t.Errorf("State = %d, want %d", ref.State, StateResolved)
}
if !ref.HasAddresses() {
t.Error("should have addresses after SetAddresses")
}
ref.SetAddresses(nil)
if ref.State != StateUnresolved {
t.Errorf("State = %d, want %d", ref.State, StateUnresolved)
}
}
func TestResolutionStateString(t *testing.T) {
tests := []struct {
state State
want string
}{
{StateUnresolved, "unresolved"},
{StateResolving, "resolving"},
{StateResolved, "resolved"},
}
for _, tt := range tests {
t.Run(tt.want, func(t *testing.T) {
if got := tt.state.String(); got != tt.want {
t.Errorf("String() = %q, want %q", got, tt.want)
}
})
}
}