6 Commits

Author SHA1 Message Date
7d380aafa4 Merge branch 'main' into fix/remove-unimplemented-stubs
Some checks failed
Check / check (pull_request) Failing after 5m42s
2026-02-28 12:08:35 +01:00
5ab217bfd2 Merge pull request 'Reduce DNS query timeout and limit root server fan-out (closes #29)' (#30) from fix/reduce-dns-timeout-and-root-fanout into main
Some checks failed
Check / check (push) Has been cancelled
Reviewed-on: #30
2026-02-28 12:07:20 +01:00
518a2cc42e Merge pull request 'doc: add TESTING.md — real DNS only, no mocks' (#34) from doc/testing-policy into main
Some checks failed
Check / check (push) Has been cancelled
Reviewed-on: #34
2026-02-28 12:06:57 +01:00
user
4cb81aac24 doc: add testing policy — real DNS only, no mocks
Some checks failed
Check / check (pull_request) Failing after 5m24s
Documents the project testing philosophy: all resolver tests must
use live DNS queries. Mocking the DNS client layer is not permitted.
Includes rationale and anti-patterns to avoid.
2026-02-22 04:28:47 -08:00
user
203b581704 Reduce DNS query timeout to 2s and limit root server fan-out to 3
Some checks failed
Check / check (pull_request) Failing after 5m57s
- Reduce queryTimeoutDuration from 5s to 2s
- Add randomRootServers() that shuffles and picks 3 root servers
- Replace all rootServerList() call sites with randomRootServers()
- Keep maxRetries = 2

Closes #29
2026-02-22 03:35:16 -08:00
clawbot
d0220e5814 fix: remove ErrNotImplemented stub — resolver, port, and TLS checks are fully implemented (closes #16)
Some checks failed
Check / check (pull_request) Failing after 5m27s
The ErrNotImplemented sentinel error was dead code left over from
initial scaffolding. The resolver performs real iterative DNS
lookups from root servers, PortCheck does TCP connection checks,
and TLSCheck verifies TLS certificates and expiry. Removed the
unused error constant.
2026-02-21 00:55:38 -08:00
4 changed files with 103 additions and 552 deletions

34
TESTING.md Normal file
View File

@@ -0,0 +1,34 @@
# Testing Policy
## DNS Resolution Tests
All resolver tests **MUST** use live queries against real DNS servers.
No mocking of the DNS client layer is permitted.
### Rationale
The resolver performs iterative resolution from root nameservers through
the full delegation chain. Mocked responses cannot faithfully represent
the variety of real-world DNS behavior (truncation, referrals, glue
records, DNSSEC, varied response times, EDNS, etc.). Testing against
real servers ensures the resolver works correctly in production.
### Constraints
- Tests hit real DNS infrastructure and require network access
- Test duration depends on network conditions; timeout tuning keeps
the suite within the 30-second target
- Query timeout is calibrated to 3× maximum antipodal RTT (~300ms)
plus processing margin
- Root server fan-out is limited to reduce parallel query load
- Flaky failures from transient network issues are acceptable and
should be investigated as potential resolver bugs, not papered over
with mocks or skip flags
### What NOT to do
- **Do not mock `DNSClient`** for resolver tests (the mock constructor
exists for unit-testing other packages that consume the resolver)
- **Do not add `-short` flags** to skip slow tests
- **Do not increase `-timeout`** to hide hanging queries
- **Do not modify linter configuration** to suppress findings

View File

@@ -4,11 +4,6 @@ import "errors"
// Sentinel errors returned by the resolver. // Sentinel errors returned by the resolver.
var ( var (
// ErrNotImplemented indicates a method is stubbed out.
ErrNotImplemented = errors.New(
"resolver not yet implemented",
)
// ErrNoNameservers is returned when no authoritative NS // ErrNoNameservers is returned when no authoritative NS
// could be discovered for a domain. // could be discovered for a domain.
ErrNoNameservers = errors.New( ErrNoNameservers = errors.New(

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"math/rand"
"net" "net"
"sort" "sort"
"strings" "strings"
@@ -13,7 +14,7 @@ import (
) )
const ( const (
queryTimeoutDuration = 5 * time.Second queryTimeoutDuration = 2 * time.Second
maxRetries = 2 maxRetries = 2
maxDelegation = 20 maxDelegation = 20
timeoutMultiplier = 2 timeoutMultiplier = 2
@@ -41,6 +42,22 @@ func rootServerList() []string {
} }
} }
const maxRootServers = 3
// randomRootServers returns a shuffled subset of root servers.
func randomRootServers() []string {
all := rootServerList()
rand.Shuffle(len(all), func(i, j int) {
all[i], all[j] = all[j], all[i]
})
if len(all) > maxRootServers {
return all[:maxRootServers]
}
return all
}
func checkCtx(ctx context.Context) error { func checkCtx(ctx context.Context) error {
err := ctx.Err() err := ctx.Err()
if err != nil { if err != nil {
@@ -302,7 +319,7 @@ func (r *Resolver) resolveNSRecursive(
msg.SetQuestion(domain, dns.TypeNS) msg.SetQuestion(domain, dns.TypeNS)
msg.RecursionDesired = true msg.RecursionDesired = true
for _, ip := range rootServerList()[:3] { for _, ip := range randomRootServers() {
if checkCtx(ctx) != nil { if checkCtx(ctx) != nil {
return nil, ErrContextCanceled return nil, ErrContextCanceled
} }
@@ -333,7 +350,7 @@ func (r *Resolver) resolveARecord(
msg.SetQuestion(hostname, dns.TypeA) msg.SetQuestion(hostname, dns.TypeA)
msg.RecursionDesired = true msg.RecursionDesired = true
for _, ip := range rootServerList()[:3] { for _, ip := range randomRootServers() {
if checkCtx(ctx) != nil { if checkCtx(ctx) != nil {
return nil, ErrContextCanceled return nil, ErrContextCanceled
} }
@@ -385,7 +402,7 @@ func (r *Resolver) FindAuthoritativeNameservers(
candidate := strings.Join(labels[i:], ".") + "." candidate := strings.Join(labels[i:], ".") + "."
nsNames, err := r.followDelegation( nsNames, err := r.followDelegation(
ctx, candidate, rootServerList(), ctx, candidate, randomRootServers(),
) )
if err == nil && len(nsNames) > 0 { if err == nil && len(nsNames) > 0 {
sort.Strings(nsNames) sort.Strings(nsNames)

View File

@@ -2,7 +2,6 @@ package resolver_test
import ( import (
"context" "context"
"fmt"
"log/slog" "log/slog"
"net" "net"
"os" "os"
@@ -11,504 +10,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"sneak.berlin/go/dnswatcher/internal/resolver" "sneak.berlin/go/dnswatcher/internal/resolver"
) )
// ----------------------------------------------------------------
// Mock DNS client
// ----------------------------------------------------------------
// mockDNSClient implements resolver.DNSClient with canned responses.
type mockDNSClient struct {
handlers map[string]func(msg *dns.Msg) *dns.Msg
}
func newMockClient() *mockDNSClient {
return &mockDNSClient{
handlers: make(map[string]func(msg *dns.Msg) *dns.Msg),
}
}
func (m *mockDNSClient) ExchangeContext(
ctx context.Context,
msg *dns.Msg,
addr string,
) (*dns.Msg, time.Duration, error) {
err := ctx.Err()
if err != nil {
return nil, 0, err
}
host, _, _ := net.SplitHostPort(addr)
if host == "" {
host = addr
}
qname := msg.Question[0].Name
qtype := dns.TypeToString[msg.Question[0].Qtype]
resp := m.findHandler(host, qname, qtype, msg)
return resp, time.Millisecond, nil
}
func (m *mockDNSClient) findHandler(
host, qname, qtype string,
msg *dns.Msg,
) *dns.Msg {
key := fmt.Sprintf(
"%s|%s|%s", host, strings.ToLower(qname), qtype,
)
if h, ok := m.handlers[key]; ok {
return h(msg)
}
wildKey := fmt.Sprintf(
"*|%s|%s", strings.ToLower(qname), qtype,
)
if h, ok := m.handlers[wildKey]; ok {
return h(msg)
}
resp := new(dns.Msg)
resp.SetReply(msg)
return resp
}
func (m *mockDNSClient) on(
server, qname, qtype string,
handler func(msg *dns.Msg) *dns.Msg,
) {
key := fmt.Sprintf(
"%s|%s|%s",
server, dns.Fqdn(strings.ToLower(qname)), qtype,
)
m.handlers[key] = handler
}
// ----------------------------------------------------------------
// Response builders
// ----------------------------------------------------------------
func referralResponse(
msg *dns.Msg,
nsNames []string,
glue map[string]string,
) *dns.Msg {
resp := new(dns.Msg)
resp.SetReply(msg)
for _, ns := range nsNames {
resp.Ns = append(resp.Ns, &dns.NS{
Hdr: dns.RR_Header{
Name: msg.Question[0].Name,
Rrtype: dns.TypeNS,
Class: dns.ClassINET,
Ttl: 3600,
},
Ns: dns.Fqdn(ns),
})
}
for name, ip := range glue {
resp.Extra = append(resp.Extra, &dns.A{
Hdr: dns.RR_Header{
Name: dns.Fqdn(name),
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 3600,
},
A: net.ParseIP(ip),
})
}
return resp
}
func nsAnswerResponse(
msg *dns.Msg, nsNames []string,
) *dns.Msg {
resp := new(dns.Msg)
resp.SetReply(msg)
for _, ns := range nsNames {
resp.Answer = append(resp.Answer, &dns.NS{
Hdr: dns.RR_Header{
Name: msg.Question[0].Name,
Rrtype: dns.TypeNS,
Class: dns.ClassINET,
Ttl: 3600,
},
Ns: dns.Fqdn(ns),
})
}
return resp
}
func nxdomainResponse(msg *dns.Msg) *dns.Msg {
resp := new(dns.Msg)
resp.SetReply(msg)
resp.Rcode = dns.RcodeNameError
return resp
}
func aResponse(
msg *dns.Msg, name string, ip string,
) *dns.Msg {
resp := new(dns.Msg)
resp.SetReply(msg)
resp.Answer = append(resp.Answer, &dns.A{
Hdr: dns.RR_Header{
Name: dns.Fqdn(name), Rrtype: dns.TypeA,
Class: dns.ClassINET, Ttl: 300,
},
A: net.ParseIP(ip),
})
return resp
}
func aaaaResponse(
msg *dns.Msg, name string, ip string,
) *dns.Msg {
resp := new(dns.Msg)
resp.SetReply(msg)
resp.Answer = append(resp.Answer, &dns.AAAA{
Hdr: dns.RR_Header{
Name: dns.Fqdn(name), Rrtype: dns.TypeAAAA,
Class: dns.ClassINET, Ttl: 300,
},
AAAA: net.ParseIP(ip),
})
return resp
}
func emptyResponse(msg *dns.Msg) *dns.Msg {
resp := new(dns.Msg)
resp.SetReply(msg)
return resp
}
// ----------------------------------------------------------------
// Mock DNS hierarchy setup
// ----------------------------------------------------------------
// mockData holds all test DNS hierarchy configuration.
type mockData struct {
tldNS []string
tldGlue map[string]string
exNS []string
exGlue map[string]string
cfNS []string
cfGlue map[string]string
}
func newMockData() mockData {
return mockData{
tldNS: []string{"ns1.tld.com", "ns2.tld.com"},
tldGlue: map[string]string{
"ns1.tld.com": "10.0.0.1",
"ns2.tld.com": "10.0.0.2",
},
exNS: []string{
"ns1.example.com", "ns2.example.com",
"ns3.example.com",
},
exGlue: map[string]string{
"ns1.example.com": "10.1.0.1",
"ns2.example.com": "10.1.0.2",
"ns3.example.com": "10.1.0.3",
},
cfNS: []string{
"ns1.cloudflare.com", "ns2.cloudflare.com",
},
cfGlue: map[string]string{
"ns1.cloudflare.com": "10.2.0.1",
"ns2.cloudflare.com": "10.2.0.2",
},
}
}
func rootIPList() []string {
return []string{
"198.41.0.4", "170.247.170.2", "192.33.4.12",
"199.7.91.13", "192.203.230.10", "192.5.5.241",
"192.112.36.4", "198.97.190.53", "192.36.148.17",
"192.58.128.30", "193.0.14.129", "199.7.83.42",
"202.12.27.33",
}
}
func allQueryTypes() []string {
return []string{
"NS", "A", "AAAA", "CNAME", "MX", "TXT", "SRV", "CAA",
}
}
func setupRootDelegations(
m *mockDNSClient,
tNS []string,
tGlue map[string]string,
) {
domains := []string{
"example.com.", "www.example.com.",
"this-surely-does-not-exist-xyz.example.com.",
"cloudflare.com.",
}
for _, rootIP := range rootIPList() {
for _, domain := range domains {
for _, qtype := range allQueryTypes() {
m.on(rootIP, domain, qtype,
func(msg *dns.Msg) *dns.Msg {
return referralResponse(
msg, tNS, tGlue,
)
},
)
}
}
}
}
func setupRootARecords(m *mockDNSClient) {
nsIPs := map[string]string{
"ns1.example.com.": "10.1.0.1",
"ns2.example.com.": "10.1.0.2",
"ns3.example.com.": "10.1.0.3",
"ns1.cloudflare.com.": "10.2.0.1",
"ns2.cloudflare.com.": "10.2.0.2",
}
for _, rootIP := range rootIPList() {
for nsName, nsIP := range nsIPs {
ip := nsIP
name := nsName
m.on(rootIP, name, "A",
func(msg *dns.Msg) *dns.Msg {
return aResponse(msg, name, ip)
},
)
}
}
}
func setupTLDDelegations(
m *mockDNSClient,
exNS []string,
exGlue map[string]string,
cfNS []string,
cfGlue map[string]string,
) {
tldIPs := []string{"10.0.0.1", "10.0.0.2"}
exDomains := []string{
"example.com.", "www.example.com.",
"this-surely-does-not-exist-xyz.example.com.",
}
for _, tldIP := range tldIPs {
for _, domain := range exDomains {
for _, qtype := range allQueryTypes() {
m.on(tldIP, domain, qtype,
func(msg *dns.Msg) *dns.Msg {
return referralResponse(
msg, exNS, exGlue,
)
},
)
}
}
for _, qtype := range allQueryTypes() {
m.on(tldIP, "cloudflare.com.", qtype,
func(msg *dns.Msg) *dns.Msg {
return referralResponse(
msg, cfNS, cfGlue,
)
},
)
}
}
}
func setupExampleNSAndA(
m *mockDNSClient, exNS []string,
) {
exIPs := []string{"10.1.0.1", "10.1.0.2", "10.1.0.3"}
for _, authIP := range exIPs {
m.on(authIP, "example.com.", "NS",
func(msg *dns.Msg) *dns.Msg {
return nsAnswerResponse(msg, exNS)
},
)
m.on(authIP, "example.com.", "A",
func(msg *dns.Msg) *dns.Msg {
return aResponse(
msg, "example.com.", "93.184.216.34",
)
},
)
m.on(authIP, "example.com.", "AAAA",
func(msg *dns.Msg) *dns.Msg {
return aaaaResponse(
msg, "example.com.",
"2606:2800:220:1:248:1893:25c8:1946",
)
},
)
}
}
func setupExampleMXAndTXT(m *mockDNSClient) {
exIPs := []string{"10.1.0.1", "10.1.0.2", "10.1.0.3"}
for _, authIP := range exIPs {
m.on(authIP, "example.com.", "MX",
func(msg *dns.Msg) *dns.Msg {
resp := new(dns.Msg)
resp.SetReply(msg)
resp.Answer = append(resp.Answer,
&dns.MX{
Hdr: dns.RR_Header{
Name: "example.com.",
Rrtype: dns.TypeMX,
Class: dns.ClassINET,
Ttl: 300,
},
Preference: 10,
Mx: "mail.example.com.",
},
&dns.MX{
Hdr: dns.RR_Header{
Name: "example.com.",
Rrtype: dns.TypeMX,
Class: dns.ClassINET,
Ttl: 300,
},
Preference: 20,
Mx: "mail2.example.com.",
},
)
return resp
},
)
m.on(authIP, "example.com.", "TXT",
func(msg *dns.Msg) *dns.Msg {
resp := new(dns.Msg)
resp.SetReply(msg)
resp.Answer = append(resp.Answer, &dns.TXT{
Hdr: dns.RR_Header{
Name: "example.com.",
Rrtype: dns.TypeTXT,
Class: dns.ClassINET,
Ttl: 300,
},
Txt: []string{
"v=spf1 include:_spf.example.com ~all",
},
})
return resp
},
)
}
}
func setupExampleSubdomains(
m *mockDNSClient, exNS []string,
) {
exIPs := []string{"10.1.0.1", "10.1.0.2", "10.1.0.3"}
for _, authIP := range exIPs {
m.on(authIP, "www.example.com.", "NS",
func(msg *dns.Msg) *dns.Msg {
return nsAnswerResponse(msg, exNS)
},
)
m.on(authIP, "www.example.com.", "A",
func(msg *dns.Msg) *dns.Msg {
return aResponse(
msg, "www.example.com.", "93.184.216.34",
)
},
)
nxName := "this-surely-does-not-exist-xyz.example.com."
for _, qtype := range allQueryTypes() {
m.on(authIP, nxName, qtype, nxdomainResponse)
}
}
}
func setupCloudflareAuthRecords(
m *mockDNSClient, cfNS []string,
) {
cfIPs := []string{"10.2.0.1", "10.2.0.2"}
for _, authIP := range cfIPs {
m.on(authIP, "cloudflare.com.", "NS",
func(msg *dns.Msg) *dns.Msg {
return nsAnswerResponse(msg, cfNS)
},
)
m.on(authIP, "cloudflare.com.", "A",
func(msg *dns.Msg) *dns.Msg {
return aResponse(
msg, "cloudflare.com.", "104.16.132.229",
)
},
)
m.on(authIP, "cloudflare.com.", "AAAA",
func(msg *dns.Msg) *dns.Msg {
return aaaaResponse(
msg, "cloudflare.com.",
"2606:4700::6810:84e5",
)
},
)
m.on(authIP, "cloudflare.com.", "MX", emptyResponse)
m.on(authIP, "cloudflare.com.", "TXT", emptyResponse)
}
}
func setupMockDNS() *mockDNSClient {
m := newMockClient()
d := newMockData()
setupRootDelegations(m, d.tldNS, d.tldGlue)
setupRootARecords(m)
setupTLDDelegations(m, d.exNS, d.exGlue, d.cfNS, d.cfGlue)
setupExampleNSAndA(m, d.exNS)
setupExampleMXAndTXT(m)
setupExampleSubdomains(m, d.exNS)
setupCloudflareAuthRecords(m, d.cfNS)
return m
}
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// Test helpers // Test helpers
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@@ -521,14 +28,14 @@ func newTestResolver(t *testing.T) *resolver.Resolver {
&slog.HandlerOptions{Level: slog.LevelDebug}, &slog.HandlerOptions{Level: slog.LevelDebug},
)) ))
return resolver.NewFromLoggerWithClient(log, setupMockDNS()) return resolver.NewFromLogger(log)
} }
func testContext(t *testing.T) context.Context { func testContext(t *testing.T) context.Context {
t.Helper() t.Helper()
ctx, cancel := context.WithTimeout( ctx, cancel := context.WithTimeout(
context.Background(), 10*time.Second, context.Background(), 60*time.Second,
) )
t.Cleanup(cancel) t.Cleanup(cancel)
@@ -565,23 +72,23 @@ func TestFindAuthoritativeNameservers_ValidDomain(
ctx := testContext(t) ctx := testContext(t)
nameservers, err := r.FindAuthoritativeNameservers( nameservers, err := r.FindAuthoritativeNameservers(
ctx, "example.com", ctx, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, nameservers) require.NotEmpty(t, nameservers)
hasExampleNS := false hasGoogleNS := false
for _, ns := range nameservers { for _, ns := range nameservers {
if strings.Contains(ns, "example") { if strings.Contains(ns, "google") {
hasExampleNS = true hasGoogleNS = true
break break
} }
} }
assert.True(t, hasExampleNS, assert.True(t, hasGoogleNS,
"expected example nameservers, got: %v", nameservers, "expected google nameservers, got: %v", nameservers,
) )
} }
@@ -594,7 +101,7 @@ func TestFindAuthoritativeNameservers_Subdomain(
ctx := testContext(t) ctx := testContext(t)
nameservers, err := r.FindAuthoritativeNameservers( nameservers, err := r.FindAuthoritativeNameservers(
ctx, "www.example.com", ctx, "www.google.com",
) )
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, nameservers) require.NotEmpty(t, nameservers)
@@ -609,7 +116,7 @@ func TestFindAuthoritativeNameservers_ReturnsSorted(
ctx := testContext(t) ctx := testContext(t)
nameservers, err := r.FindAuthoritativeNameservers( nameservers, err := r.FindAuthoritativeNameservers(
ctx, "example.com", ctx, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -629,12 +136,12 @@ func TestFindAuthoritativeNameservers_Deterministic(
ctx := testContext(t) ctx := testContext(t)
first, err := r.FindAuthoritativeNameservers( first, err := r.FindAuthoritativeNameservers(
ctx, "example.com", ctx, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
second, err := r.FindAuthoritativeNameservers( second, err := r.FindAuthoritativeNameservers(
ctx, "example.com", ctx, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -650,12 +157,12 @@ func TestFindAuthoritativeNameservers_TrailingDot(
ctx := testContext(t) ctx := testContext(t)
ns1, err := r.FindAuthoritativeNameservers( ns1, err := r.FindAuthoritativeNameservers(
ctx, "example.com", ctx, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
ns2, err := r.FindAuthoritativeNameservers( ns2, err := r.FindAuthoritativeNameservers(
ctx, "example.com.", ctx, "google.com.",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -692,10 +199,10 @@ func TestQueryNameserver_BasicA(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ns := findOneNSForDomain(t, r, ctx, "example.com") ns := findOneNSForDomain(t, r, ctx, "google.com")
resp, err := r.QueryNameserver( resp, err := r.QueryNameserver(
ctx, ns, "www.example.com", ctx, ns, "www.google.com",
) )
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, resp) require.NotNil(t, resp)
@@ -706,7 +213,7 @@ func TestQueryNameserver_BasicA(t *testing.T) {
hasRecords := len(resp.Records["A"]) > 0 || hasRecords := len(resp.Records["A"]) > 0 ||
len(resp.Records["CNAME"]) > 0 len(resp.Records["CNAME"]) > 0
assert.True(t, hasRecords, assert.True(t, hasRecords,
"expected A or CNAME records for www.example.com", "expected A or CNAME records for www.google.com",
) )
} }
@@ -740,16 +247,16 @@ func TestQueryNameserver_MX(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ns := findOneNSForDomain(t, r, ctx, "example.com") ns := findOneNSForDomain(t, r, ctx, "google.com")
resp, err := r.QueryNameserver( resp, err := r.QueryNameserver(
ctx, ns, "example.com", ctx, ns, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
mxRecords := resp.Records["MX"] mxRecords := resp.Records["MX"]
require.NotEmpty(t, mxRecords, require.NotEmpty(t, mxRecords,
"example.com should have MX records", "google.com should have MX records",
) )
} }
@@ -758,16 +265,16 @@ func TestQueryNameserver_TXT(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ns := findOneNSForDomain(t, r, ctx, "example.com") ns := findOneNSForDomain(t, r, ctx, "google.com")
resp, err := r.QueryNameserver( resp, err := r.QueryNameserver(
ctx, ns, "example.com", ctx, ns, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
txtRecords := resp.Records["TXT"] txtRecords := resp.Records["TXT"]
require.NotEmpty(t, txtRecords, require.NotEmpty(t, txtRecords,
"example.com should have TXT records", "google.com should have TXT records",
) )
hasSPF := false hasSPF := false
@@ -781,7 +288,7 @@ func TestQueryNameserver_TXT(t *testing.T) {
} }
assert.True(t, hasSPF, assert.True(t, hasSPF,
"example.com should have SPF TXT record", "google.com should have SPF TXT record",
) )
} }
@@ -790,11 +297,11 @@ func TestQueryNameserver_NXDomain(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ns := findOneNSForDomain(t, r, ctx, "example.com") ns := findOneNSForDomain(t, r, ctx, "google.com")
resp, err := r.QueryNameserver( resp, err := r.QueryNameserver(
ctx, ns, ctx, ns,
"this-surely-does-not-exist-xyz.example.com", "this-surely-does-not-exist-xyz.google.com",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -806,10 +313,10 @@ func TestQueryNameserver_RecordsSorted(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ns := findOneNSForDomain(t, r, ctx, "example.com") ns := findOneNSForDomain(t, r, ctx, "google.com")
resp, err := r.QueryNameserver( resp, err := r.QueryNameserver(
ctx, ns, "example.com", ctx, ns, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -846,11 +353,11 @@ func TestQueryNameserver_EmptyRecordsOnNXDomain(
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ns := findOneNSForDomain(t, r, ctx, "example.com") ns := findOneNSForDomain(t, r, ctx, "google.com")
resp, err := r.QueryNameserver( resp, err := r.QueryNameserver(
ctx, ns, ctx, ns,
"this-surely-does-not-exist-xyz.example.com", "this-surely-does-not-exist-xyz.google.com",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -867,15 +374,15 @@ func TestQueryNameserver_TrailingDotHandling(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ns := findOneNSForDomain(t, r, ctx, "example.com") ns := findOneNSForDomain(t, r, ctx, "google.com")
resp1, err := r.QueryNameserver( resp1, err := r.QueryNameserver(
ctx, ns, "example.com", ctx, ns, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
resp2, err := r.QueryNameserver( resp2, err := r.QueryNameserver(
ctx, ns, "example.com.", ctx, ns, "google.com.",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -893,7 +400,7 @@ func TestQueryAllNameservers_ReturnsAllNS(t *testing.T) {
ctx := testContext(t) ctx := testContext(t)
results, err := r.QueryAllNameservers( results, err := r.QueryAllNameservers(
ctx, "example.com", ctx, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, results) require.NotEmpty(t, results)
@@ -912,7 +419,7 @@ func TestQueryAllNameservers_AllReturnOK(t *testing.T) {
ctx := testContext(t) ctx := testContext(t)
results, err := r.QueryAllNameservers( results, err := r.QueryAllNameservers(
ctx, "example.com", ctx, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -934,7 +441,7 @@ func TestQueryAllNameservers_NXDomainFromAllNS(
results, err := r.QueryAllNameservers( results, err := r.QueryAllNameservers(
ctx, ctx,
"this-surely-does-not-exist-xyz.example.com", "this-surely-does-not-exist-xyz.google.com",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -956,7 +463,7 @@ func TestLookupNS_ValidDomain(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
nameservers, err := r.LookupNS(ctx, "example.com") nameservers, err := r.LookupNS(ctx, "google.com")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, nameservers) require.NotEmpty(t, nameservers)
@@ -973,7 +480,7 @@ func TestLookupNS_Sorted(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
nameservers, err := r.LookupNS(ctx, "example.com") nameservers, err := r.LookupNS(ctx, "google.com")
require.NoError(t, err) require.NoError(t, err)
assert.True(t, sort.StringsAreSorted(nameservers)) assert.True(t, sort.StringsAreSorted(nameservers))
@@ -985,11 +492,11 @@ func TestLookupNS_MatchesFindAuthoritative(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
fromLookup, err := r.LookupNS(ctx, "example.com") fromLookup, err := r.LookupNS(ctx, "google.com")
require.NoError(t, err) require.NoError(t, err)
fromFind, err := r.FindAuthoritativeNameservers( fromFind, err := r.FindAuthoritativeNameservers(
ctx, "example.com", ctx, "google.com",
) )
require.NoError(t, err) require.NoError(t, err)
@@ -1006,7 +513,7 @@ func TestResolveIPAddresses_ReturnsIPs(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, "example.com") ips, err := r.ResolveIPAddresses(ctx, "google.com")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, ips) require.NotEmpty(t, ips)
@@ -1024,7 +531,7 @@ func TestResolveIPAddresses_Deduplicated(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, "example.com") ips, err := r.ResolveIPAddresses(ctx, "google.com")
require.NoError(t, err) require.NoError(t, err)
seen := make(map[string]bool) seen := make(map[string]bool)
@@ -1041,7 +548,7 @@ func TestResolveIPAddresses_Sorted(t *testing.T) {
r := newTestResolver(t) r := newTestResolver(t)
ctx := testContext(t) ctx := testContext(t)
ips, err := r.ResolveIPAddresses(ctx, "example.com") ips, err := r.ResolveIPAddresses(ctx, "google.com")
require.NoError(t, err) require.NoError(t, err)
assert.True(t, sort.StringsAreSorted(ips)) assert.True(t, sort.StringsAreSorted(ips))
@@ -1057,7 +564,7 @@ func TestResolveIPAddresses_NXDomainReturnsEmpty(
ips, err := r.ResolveIPAddresses( ips, err := r.ResolveIPAddresses(
ctx, ctx,
"this-surely-does-not-exist-xyz.example.com", "this-surely-does-not-exist-xyz.google.com",
) )
require.NoError(t, err) require.NoError(t, err)
assert.Empty(t, ips) assert.Empty(t, ips)
@@ -1087,9 +594,7 @@ func TestFindAuthoritativeNameservers_ContextCanceled(
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
cancel() cancel()
_, err := r.FindAuthoritativeNameservers( _, err := r.FindAuthoritativeNameservers(ctx, "google.com")
ctx, "example.com",
)
assert.Error(t, err) assert.Error(t, err)
} }
@@ -1101,7 +606,7 @@ func TestQueryNameserver_ContextCanceled(t *testing.T) {
cancel() cancel()
_, err := r.QueryNameserver( _, err := r.QueryNameserver(
ctx, "ns1.example.com.", "example.com", ctx, "ns1.google.com.", "google.com",
) )
assert.Error(t, err) assert.Error(t, err)
} }
@@ -1113,7 +618,7 @@ func TestQueryAllNameservers_ContextCanceled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
cancel() cancel()
_, err := r.QueryAllNameservers(ctx, "example.com") _, err := r.QueryAllNameservers(ctx, "google.com")
assert.Error(t, err) assert.Error(t, err)
} }
@@ -1124,6 +629,6 @@ func TestResolveIPAddresses_ContextCanceled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
cancel() cancel()
_, err := r.ResolveIPAddresses(ctx, "example.com") _, err := r.ResolveIPAddresses(ctx, "google.com")
assert.Error(t, err) assert.Error(t, err)
} }