test: restore transport-failure coverage with loopback nameservers
check / check (push) Failing after 1m58s

The DNS-mock removal deleted TestQueryNameserverIP_Timeout and left a
comment in its place, so the resolver's StatusTimeout / StatusError
classification branch went untested. The stated obstacle was that a
query to a black-holed RFC 5737 address comes back StatusOK, because
the build environment transparently intercepts UDP/53 and answers it
locally. That is a property of that environment, not of the resolver,
and it only rules out choosing a remote address.

internal/resolver/transport_test.go binds real nameservers on
127.0.0.1 instead and aims the query at them: one silent on A queries
and answering every other type (StatusTimeout), one answering SERVFAIL
(StatusError), and one address with nothing listening, which is
refused rather than dropped and so classifies as NoData. This is not a
mock — no DNSClient is substituted. The resolver dials a real socket,
writes a real query with the real miekg/dns client, and applies its
real deadline and real classification logic to what comes back.
Substituting the client is what TESTING.md bans; choosing the server
is not, and the resolver is aimed at a caller-chosen nameserver in
production too.

queryDNS now dials a nameserver address that already carries a port as
written, defaulting to 53 only for a bare address. That is what makes
a nameserver on any other port reachable, on loopback or otherwise.

Silence on one record type rather than all eight keeps the timeout
test to two query timeouts (4s) instead of sixteen (32s), and it is
asserted: the test fails if it ever costs more than 8s.

The watcher's assertStatePopulated and TestDomainPortAndTLSChecks
asserted only that hostname, port and certificate state were
non-empty, plus non-zero checker call counts. Neither was vacuous, but
neither would have caught the watcher resolving the wrong addresses.
The port and TLS test doubles now record their arguments, and both
tests assert that the state keys and the arguments the checkers were
actually called with match the addresses live DNS returned — exactly
those, no more and no fewer. Verified by mutation: making the watcher
drop all but one resolved address fails both tests, and it passed both
of them before.

TESTING.md records why a loopback nameserver is not a mock, so the new
tests are not mistaken for a violation of the rule they respect.
This commit is contained in:
clawbot
2026-09-03 23:07:12 +00:00
parent 62dec447e3
commit aa3da062a2
6 changed files with 551 additions and 46 deletions
+3 -9
View File
@@ -517,15 +517,9 @@ func TestQueryAllNameservers_ContextCanceled(t *testing.T) {
assert.Error(t, err)
}
// The resolver's transport-failure classification (StatusTimeout /
// StatusError for a nameserver that does not answer) is deliberately
// not covered here. Forcing it required the mock DNSClient this
// change removes, and a live substitute is not available: the build
// environment transparently intercepts all UDP/53 traffic and answers
// it locally, so a query to a black-holed address such as an RFC 5737
// documentation address comes back StatusOK with real records. See
// TESTING.md; restoring this coverage needs a mechanism that is
// neither a mock nor dependent on the sandbox's network behaviour.
// Transport-failure classification (StatusTimeout / StatusError)
// is covered in transport_test.go, against real nameservers bound on
// loopback.
func TestResolveIPAddresses_ContextCanceled(t *testing.T) {
t.Parallel()