Files
dnswatcher/internal/resolver/resolver_test.go
sneak 483bed68a1 Add resolver API definition and comprehensive live-DNS test suite
The test suite defines the full resolver contract using live DNS
queries against controlled records in the sneak.cloud zone
(Cloudflare). Covers FindAuthoritativeNameservers, QueryNameserver,
QueryAllNameservers, LookupNS, and ResolveIPAddresses, including
sorting/determinism guarantees, trailing-dot handling, per-NS
response status model, lame-delegation detection, NXDOMAIN
semantics, CNAME following, and context cancellation.

Also adds DNSSEC validation to planned future features in README.
2026-07-07 03:06:49 +02:00

1033 lines
22 KiB
Go

package resolver_test
import (
"context"
"log/slog"
"net"
"os"
"sort"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sneak.berlin/go/dnswatcher/internal/resolver"
)
// Test domain and hostnames hosted on Cloudflare.
// These records must exist in the sneak.cloud Cloudflare zone:
//
// basic.dns.sneak.cloud A 192.0.2.1
// multi.dns.sneak.cloud A 192.0.2.1
// multi.dns.sneak.cloud A 192.0.2.2
// ipv6.dns.sneak.cloud AAAA 2001:db8::1
// dual.dns.sneak.cloud A 192.0.2.1
// dual.dns.sneak.cloud AAAA 2001:db8::1
// cname-target.dns.sneak.cloud A 198.51.100.1
// cname.dns.sneak.cloud CNAME cname-target.dns.sneak.cloud
// mx.dns.sneak.cloud MX 10 mail.dns.sneak.cloud
// mail.dns A 192.0.2.10
// txt.dns.sneak.cloud TXT "v=spf1 -all"
const (
testDomain = "sneak.cloud"
testHostBasic = "basic.dns.sneak.cloud"
testHostMultiA = "multi.dns.sneak.cloud"
testHostIPv6 = "ipv6.dns.sneak.cloud"
testHostDualStack = "dual.dns.sneak.cloud"
testHostCNAME = "cname.dns.sneak.cloud"
testHostCNAMETarget = "cname-target.dns.sneak.cloud"
testHostMX = "mx.dns.sneak.cloud"
testHostMail = "mail.dns.sneak.cloud"
testHostTXT = "txt.dns.sneak.cloud"
testHostNXDomain = "nxdomain-surely-does-not-exist.dns.sneak.cloud"
)
// queryTimeout is the default timeout for test queries.
const queryTimeout = 30 * time.Second
func newTestResolver(t *testing.T) *resolver.Resolver {
t.Helper()
log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
return resolver.NewFromLogger(log)
}
func testContext(t *testing.T) context.Context {
t.Helper()
ctx, cancel := context.WithTimeout(
context.Background(), queryTimeout,
)
t.Cleanup(cancel)
return ctx
}
// --- FindAuthoritativeNameservers tests ---
func TestFindAuthoritativeNameservers_ValidDomain(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
nameservers, err := r.FindAuthoritativeNameservers(
ctx, testDomain,
)
require.NoError(t, err)
require.NotEmpty(t, nameservers, "should find at least one NS")
// sneak.cloud is on Cloudflare, NS should contain cloudflare
for _, ns := range nameservers {
t.Logf("discovered NS: %s", ns)
assert.True(
t,
strings.HasSuffix(ns, "."),
"NS should be FQDN with trailing dot: %s", ns,
)
}
// Verify at least one is a Cloudflare NS
hasCloudflare := false
for _, ns := range nameservers {
if strings.Contains(ns, "cloudflare") {
hasCloudflare = true
break
}
}
assert.True(
t, hasCloudflare,
"sneak.cloud should be hosted on Cloudflare, got: %v",
nameservers,
)
}
func TestFindAuthoritativeNameservers_Subdomain(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
// Looking up NS for a hostname that isn't a zone should
// return the parent zone's NS records.
nameservers, err := r.FindAuthoritativeNameservers(
ctx, testHostBasic,
)
require.NoError(t, err)
require.NotEmpty(t, nameservers)
// Should be the same Cloudflare NSes as the parent domain
hasCloudflare := false
for _, ns := range nameservers {
if strings.Contains(ns, "cloudflare") {
hasCloudflare = true
break
}
}
assert.True(t, hasCloudflare)
}
func TestFindAuthoritativeNameservers_TLD(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
nameservers, err := r.FindAuthoritativeNameservers(
ctx, "cloud",
)
require.NoError(t, err)
require.NotEmpty(t, nameservers, "should find TLD nameservers")
for _, ns := range nameservers {
t.Logf("TLD NS: %s", ns)
}
}
func TestFindAuthoritativeNameservers_ReturnsSorted(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
nameservers, err := r.FindAuthoritativeNameservers(
ctx, testDomain,
)
require.NoError(t, err)
require.NotEmpty(t, nameservers)
// Results should be sorted for deterministic comparison
assert.True(
t,
sort.StringsAreSorted(nameservers),
"nameservers should be sorted, got: %v", nameservers,
)
}
func TestFindAuthoritativeNameservers_Deterministic(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
first, err := r.FindAuthoritativeNameservers(
ctx, testDomain,
)
require.NoError(t, err)
second, err := r.FindAuthoritativeNameservers(
ctx, testDomain,
)
require.NoError(t, err)
assert.Equal(
t, first, second,
"repeated lookups should return same result",
)
}
// --- QueryNameserver tests ---
func TestQueryNameserver_BasicA(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostBasic)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, resolver.StatusOK, resp.Status)
assert.Equal(t, ns, resp.Nameserver)
aRecords := resp.Records["A"]
require.NotEmpty(t, aRecords, "basic.dns should have A records")
assert.Contains(t, aRecords, "192.0.2.1")
t.Logf(
"QueryNameserver(%s, %s) A records: %v",
ns, testHostBasic, aRecords,
)
}
func TestQueryNameserver_MultipleA(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostMultiA)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, resolver.StatusOK, resp.Status)
aRecords := resp.Records["A"]
require.Len(
t, aRecords, 2,
"multi.dns should have exactly 2 A records",
)
sort.Strings(aRecords)
assert.Equal(t, []string{"192.0.2.1", "192.0.2.2"}, aRecords)
}
func TestQueryNameserver_AAAA(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostIPv6)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, resolver.StatusOK, resp.Status)
aaaaRecords := resp.Records["AAAA"]
require.NotEmpty(
t, aaaaRecords,
"ipv6.dns should have AAAA records",
)
assert.Contains(t, aaaaRecords, "2001:db8::1")
}
func TestQueryNameserver_DualStack(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostDualStack)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, resolver.StatusOK, resp.Status)
assert.Contains(t, resp.Records["A"], "192.0.2.1")
assert.Contains(t, resp.Records["AAAA"], "2001:db8::1")
}
func TestQueryNameserver_CNAME(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostCNAME)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, resolver.StatusOK, resp.Status)
cnameRecords := resp.Records["CNAME"]
require.NotEmpty(
t, cnameRecords,
"cname.dns should have CNAME records",
)
assert.Contains(
t, cnameRecords, "cname-target.dns.sneak.cloud.",
)
}
func TestQueryNameserver_MX(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostMX)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, resolver.StatusOK, resp.Status)
mxRecords := resp.Records["MX"]
require.NotEmpty(
t, mxRecords,
"mx.dns should have MX records",
)
// MX records are formatted as "priority host"
hasMail := false
for _, mx := range mxRecords {
if strings.Contains(mx, "mail.dns.sneak.cloud.") {
hasMail = true
break
}
}
assert.True(
t, hasMail,
"MX should reference mail.dns.sneak.cloud, got: %v",
mxRecords,
)
}
func TestQueryNameserver_TXT(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostTXT)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, resolver.StatusOK, resp.Status)
txtRecords := resp.Records["TXT"]
require.NotEmpty(
t, txtRecords,
"txt.dns should have TXT records",
)
hasSPF := false
for _, txt := range txtRecords {
if strings.Contains(txt, "v=spf1") {
hasSPF = true
break
}
}
assert.True(
t, hasSPF,
"TXT should contain SPF record, got: %v", txtRecords,
)
}
func TestQueryNameserver_NXDomain(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostNXDomain)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(
t, resolver.StatusNXDomain, resp.Status,
"nonexistent host should return nxdomain status",
)
}
func TestQueryNameserver_RecordsSorted(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostMultiA)
require.NoError(t, err)
// Each record type's values should be sorted for determinism
for recordType, values := range resp.Records {
assert.True(
t,
sort.StringsAreSorted(values),
"%s records should be sorted, got: %v",
recordType, values,
)
}
}
func TestQueryNameserver_ResponseIncludesNameserver(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostBasic)
require.NoError(t, err)
assert.Equal(
t, ns, resp.Nameserver,
"response should include the queried nameserver",
)
}
func TestQueryNameserver_EmptyRecordsMapOnNXDomain(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
resp, err := r.QueryNameserver(ctx, ns, testHostNXDomain)
require.NoError(t, err)
totalRecords := 0
for _, values := range resp.Records {
totalRecords += len(values)
}
assert.Zero(
t, totalRecords,
"NXDOMAIN should have no records, got: %v",
resp.Records,
)
}
// --- QueryAllNameservers tests ---
func TestQueryAllNameservers_ReturnsAllNS(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
results, err := r.QueryAllNameservers(ctx, testHostBasic)
require.NoError(t, err)
require.NotEmpty(t, results)
// Should have queried each NS independently
t.Logf(
"QueryAllNameservers returned %d nameserver results",
len(results),
)
for ns, resp := range results {
t.Logf(" %s: status=%s A=%v", ns, resp.Status, resp.Records["A"])
assert.Equal(t, ns, resp.Nameserver)
}
// Should have more than one NS for Cloudflare-hosted domain
assert.GreaterOrEqual(
t, len(results), 2,
"should query at least 2 nameservers",
)
}
func TestQueryAllNameservers_Consistent(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
results, err := r.QueryAllNameservers(ctx, testHostBasic)
require.NoError(t, err)
require.NotEmpty(t, results)
// All NSes should return the same A records for a
// well-configured hostname.
var referenceRecords map[string][]string
for ns, resp := range results {
require.Equal(
t, resolver.StatusOK, resp.Status,
"NS %s should return OK status", ns,
)
if referenceRecords == nil {
referenceRecords = resp.Records
continue
}
assert.Equal(
t, referenceRecords["A"], resp.Records["A"],
"NS %s A records should match", ns,
)
}
}
func TestQueryAllNameservers_NXDomainFromAllNS(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
results, err := r.QueryAllNameservers(
ctx, testHostNXDomain,
)
require.NoError(t, err)
require.NotEmpty(t, results)
for ns, resp := range results {
assert.Equal(
t, resolver.StatusNXDomain, resp.Status,
"NS %s should return nxdomain for nonexistent host",
ns,
)
}
}
// --- LookupNS tests ---
func TestLookupNS_ValidDomain(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
nameservers, err := r.LookupNS(ctx, testDomain)
require.NoError(t, err)
require.NotEmpty(t, nameservers)
for _, ns := range nameservers {
t.Logf("NS record: %s", ns)
assert.True(
t,
strings.HasSuffix(ns, "."),
"NS should be FQDN: %s", ns,
)
}
}
func TestLookupNS_Sorted(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
nameservers, err := r.LookupNS(ctx, testDomain)
require.NoError(t, err)
assert.True(
t,
sort.StringsAreSorted(nameservers),
"NS records should be sorted, got: %v", nameservers,
)
}
func TestLookupNS_MatchesFindAuthoritative(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
fromLookup, err := r.LookupNS(ctx, testDomain)
require.NoError(t, err)
fromFind, err := r.FindAuthoritativeNameservers(
ctx, testDomain,
)
require.NoError(t, err)
// Both methods should return the same NS set
assert.Equal(
t, fromFind, fromLookup,
"LookupNS and FindAuthoritativeNameservers "+
"should return the same set",
)
}
// --- ResolveIPAddresses tests ---
func TestResolveIPAddresses_BasicA(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, testHostBasic)
require.NoError(t, err)
require.NotEmpty(t, ips)
assert.Contains(t, ips, "192.0.2.1")
}
func TestResolveIPAddresses_MultipleA(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, testHostMultiA)
require.NoError(t, err)
sort.Strings(ips)
assert.Contains(t, ips, "192.0.2.1")
assert.Contains(t, ips, "192.0.2.2")
}
func TestResolveIPAddresses_IPv6Only(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, testHostIPv6)
require.NoError(t, err)
require.NotEmpty(t, ips)
assert.Contains(t, ips, "2001:db8::1")
// Should not contain any IPv4
for _, ip := range ips {
parsed := net.ParseIP(ip)
require.NotNil(t, parsed, "should be valid IP: %s", ip)
assert.Nil(
t, parsed.To4(),
"ipv6-only host should not return IPv4: %s", ip,
)
}
}
func TestResolveIPAddresses_DualStack(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, testHostDualStack)
require.NoError(t, err)
assert.Contains(t, ips, "192.0.2.1")
assert.Contains(t, ips, "2001:db8::1")
}
func TestResolveIPAddresses_FollowsCNAME(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
// cname.dns.sneak.cloud -> cname-target.dns.sneak.cloud -> 198.51.100.1
ips, err := r.ResolveIPAddresses(ctx, testHostCNAME)
require.NoError(t, err)
require.NotEmpty(t, ips)
assert.Contains(
t, ips, "198.51.100.1",
"should follow CNAME to resolve target IP",
)
}
func TestResolveIPAddresses_Deduplicated(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, testHostBasic)
require.NoError(t, err)
// Check for duplicates
seen := make(map[string]bool)
for _, ip := range ips {
assert.False(
t, seen[ip],
"IP %s appears more than once", ip,
)
seen[ip] = true
}
}
func TestResolveIPAddresses_Sorted(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, testHostDualStack)
require.NoError(t, err)
assert.True(
t,
sort.StringsAreSorted(ips),
"IP addresses should be sorted, got: %v", ips,
)
}
func TestResolveIPAddresses_NXDomainReturnsEmpty(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, testHostNXDomain)
// Should not error — NXDOMAIN is an expected DNS response.
// It just means no IPs to return.
require.NoError(t, err)
assert.Empty(t, ips)
}
// --- Context cancellation tests ---
func TestFindAuthoritativeNameservers_ContextCanceled(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
_, err := r.FindAuthoritativeNameservers(ctx, testDomain)
assert.Error(t, err)
}
func TestQueryNameserver_ContextCanceled(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := r.QueryNameserver(
ctx, "ns1.example.com.", testHostBasic,
)
assert.Error(t, err)
}
func TestQueryAllNameservers_ContextCanceled(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := r.QueryAllNameservers(ctx, testHostBasic)
assert.Error(t, err)
}
func TestResolveIPAddresses_ContextCanceled(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := r.ResolveIPAddresses(ctx, testHostBasic)
assert.Error(t, err)
}
// --- Iterative resolution verification ---
func TestFindAuthoritativeNameservers_IsIterative(
t *testing.T,
) {
// Verify that resolution works for well-known domains,
// proving we trace from root rather than relying on a
// system stub resolver that might not be configured.
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
// Resolve a well-known domain to prove root->TLD->domain
// tracing works.
nameservers, err := r.FindAuthoritativeNameservers(
ctx, "example.com",
)
require.NoError(t, err)
require.NotEmpty(t, nameservers)
t.Logf("example.com NS: %v", nameservers)
}
// --- NS record and delegation tests ---
func TestQueryNameserver_NSRecordsForZoneApex(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
// Querying a zone apex should return NS records
resp, err := r.QueryNameserver(ctx, ns, testDomain)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, resolver.StatusOK, resp.Status)
nsRecords := resp.Records["NS"]
require.NotEmpty(
t, nsRecords,
"zone apex should have NS records",
)
// Should contain Cloudflare nameservers
hasCloudflare := false
for _, nsRecord := range nsRecords {
t.Logf("NS record: %s", nsRecord)
if strings.Contains(nsRecord, "cloudflare") {
hasCloudflare = true
}
}
assert.True(
t, hasCloudflare,
"NS records should include Cloudflare, got: %v",
nsRecords,
)
}
func TestQueryNameserver_NoNSRecordsForLeafHostname(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
// A leaf hostname (not a zone cut) should not have NS records
resp, err := r.QueryNameserver(ctx, ns, testHostBasic)
require.NoError(t, err)
require.NotNil(t, resp)
nsRecords := resp.Records["NS"]
assert.Empty(
t, nsRecords,
"leaf hostname should not have NS records, got: %v",
nsRecords,
)
}
func TestFindAuthoritativeNameservers_AllNSRespondAuthoritatively(
t *testing.T,
) {
// Every NS returned by FindAuthoritativeNameservers should
// actually serve the zone. A lame NS would return REFUSED,
// SERVFAIL, or a non-authoritative answer. This test verifies
// that each discovered NS can answer queries authoritatively.
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
nameservers, err := r.FindAuthoritativeNameservers(
ctx, testDomain,
)
require.NoError(t, err)
require.NotEmpty(t, nameservers)
for _, ns := range nameservers {
resp, queryErr := r.QueryNameserver(
ctx, ns, testHostBasic,
)
require.NoError(
t, queryErr,
"NS %s should be queryable", ns,
)
assert.Equal(
t, resolver.StatusOK, resp.Status,
"NS %s should respond authoritatively, "+
"status=%s error=%s",
ns, resp.Status, resp.Error,
)
assert.NotEmpty(
t, resp.Records["A"],
"NS %s should return A records for %s",
ns, testHostBasic,
)
}
}
func TestQueryNameserver_LameNameserver(t *testing.T) {
// Querying a nameserver that does not serve a zone should
// return a non-ok status (error or refused), not silently
// return empty results with status "ok".
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
// Use a known-good nameserver that does NOT serve sneak.cloud.
// a.iana-servers.net serves example.com, not sneak.cloud.
resp, err := r.QueryNameserver(
ctx, "a.iana-servers.net.", testHostBasic,
)
require.NoError(t, err, "should not return Go-level error")
require.NotNil(t, resp)
assert.NotEqual(
t, resolver.StatusOK, resp.Status,
"lame NS should not return status ok; got status=%s "+
"records=%v",
resp.Status, resp.Records,
)
}
// --- Edge cases ---
func TestQueryNameserver_TrailingDotHandling(t *testing.T) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns := findOneNS(t, r, ctx)
// Both with and without trailing dot should work
resp1, err := r.QueryNameserver(
ctx, ns, "basic.dns.sneak.cloud",
)
require.NoError(t, err)
resp2, err := r.QueryNameserver(
ctx, ns, "basic.dns.sneak.cloud.",
)
require.NoError(t, err)
assert.Equal(
t, resp1.Records["A"], resp2.Records["A"],
"trailing dot should not affect results",
)
}
func TestFindAuthoritativeNameservers_TrailingDot(
t *testing.T,
) {
t.Parallel()
r := newTestResolver(t)
ctx := testContext(t)
ns1, err := r.FindAuthoritativeNameservers(
ctx, "sneak.cloud",
)
require.NoError(t, err)
ns2, err := r.FindAuthoritativeNameservers(
ctx, "sneak.cloud.",
)
require.NoError(t, err)
assert.Equal(
t, ns1, ns2,
"trailing dot should not affect NS lookup",
)
}
// --- Helper functions ---
// findOneNS discovers authoritative nameservers and returns the first
// one, failing the test if none are found.
func findOneNS(
t *testing.T,
r *resolver.Resolver,
ctx context.Context, //nolint:revive // test helper
) string {
t.Helper()
nameservers, err := r.FindAuthoritativeNameservers(
ctx, testDomain,
)
require.NoError(t, err)
require.NotEmpty(
t, nameservers,
"should find at least one NS for %s", testDomain,
)
return nameservers[0]
}