Files
dnshelper/resolver/fallback_test.go
T

140 lines
3.9 KiB
Go

package resolver_test
import (
"context"
"io"
"testing"
"time"
"dns-helper/resolver"
"golang.org/x/net/dns/dnsmessage"
)
// ---------------------------------------------------------------------------
// ParallelAFallback tests (T013)
// ---------------------------------------------------------------------------
func TestParallelAFallback_FirstSuccessWins(t *testing.T) {
qname := mustNewName("example.com.")
// Server A: returns A records.
addrA := startFakeDNSMulti(t, func(query []byte) []byte {
return buildAResponse(queryID(query), qname, [][4]byte{{1, 2, 3, 4}})
})
// Server B: returns NXDOMAIN.
addrB := startFakeDNSMulti(t, func(query []byte) []byte {
return buildNXDOMAINResponse(queryID(query), qname)
})
ips, err := resolver.ParallelAFallback(context.Background(), io.Discard, []string{addrA, addrB}, "example.com", 2*time.Second)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(ips) == 0 {
t.Error("expected at least one IP")
}
}
func TestParallelAFallback_AllNXDOMAIN(t *testing.T) {
qname := mustNewName("noexist.example.com.")
addrA := startFakeDNSMulti(t, func(query []byte) []byte {
return buildNXDOMAINResponse(queryID(query), qname)
})
addrB := startFakeDNSMulti(t, func(query []byte) []byte {
return buildNXDOMAINResponse(queryID(query), qname)
})
_, err := resolver.ParallelAFallback(context.Background(), io.Discard, []string{addrA, addrB}, "noexist.example.com", 2*time.Second)
if err == nil {
t.Fatal("expected error for all-NXDOMAIN responses")
}
}
func TestParallelAFallback_OneTimeout(t *testing.T) {
qname := mustNewName("example.com.")
silentAddr := startSilentDNS(t)
responsiveAddr := startFakeDNSMulti(t, func(query []byte) []byte {
return buildAResponse(queryID(query), qname, [][4]byte{{5, 6, 7, 8}})
})
ips, err := resolver.ParallelAFallback(
context.Background(),
io.Discard,
[]string{silentAddr, responsiveAddr},
"example.com",
300*time.Millisecond,
)
if err != nil {
t.Fatalf("expected success from responsive resolver, got error: %v", err)
}
if len(ips) == 0 {
t.Error("expected IPs from responsive resolver")
}
}
func TestParallelAFallback_AllFail(t *testing.T) {
silentA := startSilentDNS(t)
silentB := startSilentDNS(t)
_, err := resolver.ParallelAFallback(
context.Background(),
io.Discard,
[]string{silentA, silentB},
"example.com",
100*time.Millisecond,
)
if err == nil {
t.Fatal("expected error when all resolvers fail")
}
}
func TestParallelAFallback_EmptyResolvers(t *testing.T) {
_, err := resolver.ParallelAFallback(context.Background(), io.Discard, nil, "example.com", time.Second)
if err == nil {
t.Fatal("expected error for empty resolver list")
}
}
func TestParallelAFallback_CNAMEOnlyNoARecord(t *testing.T) {
// Server returns a CNAME but no A record — queryA will return "no A records" error.
// ParallelAFallback should return an error.
qname := mustNewName("www.example.com.")
cnameTarget := mustNewName("real.example.com.")
addrA := startFakeDNSMulti(t, func(query []byte) []byte {
return buildCNAMEResponse(queryID(query), qname, cnameTarget)
})
_, err := resolver.ParallelAFallback(context.Background(), io.Discard, []string{addrA}, "www.example.com", 2*time.Second)
if err == nil {
t.Error("expected error for CNAME-only response (no A records)")
}
}
// ---------------------------------------------------------------------------
// Helper: build NS NXDOMAIN response (used in other test files)
// ---------------------------------------------------------------------------
func buildNSNXDOMAIN(id uint16, name dnsmessage.Name) []byte {
msg := dnsmessage.Message{
Header: dnsmessage.Header{
ID: id,
Response: true,
RCode: dnsmessage.RCodeNameError,
},
Questions: []dnsmessage.Question{{
Name: name,
Type: dnsmessage.TypeNS,
Class: dnsmessage.ClassINET,
}},
}
packed, err := msg.Pack()
if err != nil {
panic("buildNSNXDOMAIN: pack failed: " + err.Error())
}
return packed
}