package resolver_test import ( "context" "ekdns/resolver" "io" "testing" "time" "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 }