package resolver import ( "strconv" "syscall" "testing" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TestClassifyResponse sets a nameserver's status from the results of // its queries, built here. One that answered some record types, even // with no records, has not failed when its query for another type got // no usable reply, whatever the reason; one whose every query got none // has. func TestClassifyResponse(t *testing.T) { t.Parallel() tests := []struct { name string results queryState wantStatus string wantError string }{ { "some types answered with no records, another timed out", queryState{answered: true, gotTimeout: true}, StatusNoData, "", }, { "some types answered with no records, another got SERVFAIL", queryState{ answered: true, gotErrorReply: true, errorReply: "SERVFAIL", }, StatusNoData, "", }, { "some types answered with no records, another was refused", queryState{answered: true, gotRefused: true}, StatusNoData, "", }, { "some types answered with no records, another got a network error", queryState{answered: true, netErr: syscall.ECONNREFUSED}, StatusNoData, "", }, { "some types answered with no records, another's reply was " + "truncated and its retry over TCP failed", queryState{answered: true, netErr: ErrTruncated}, StatusNoData, "", }, { "some types answered with no records, another got a referral", queryState{answered: true, gotReferral: true}, StatusNoData, "", }, { "every query timed out", queryState{gotTimeout: true}, StatusTimeout, "all queries timed out", }, { "every query got NOTIMP", queryState{gotErrorReply: true, errorReply: "NOTIMP"}, StatusError, "server returned NOTIMP", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() resp := &NameserverResponse{Status: StatusOK} classifyResponse(resp, tt.results) assert.Equal(t, tt.wantStatus, resp.Status) assert.Equal(t, tt.wantError, resp.Error) }) } } // TestReadReply checks which replies to a query about one record type, // built here, are an answer: one with the code NOERROR or NXDOMAIN. A // reply with any other code is not, and the type's query has failed; a // nameserver whose only reply it is has failed, and Error gives the // code, or its number when the code has no name. func TestReadReply(t *testing.T) { t.Parallel() tests := []struct { rcode int wantStatus string wantError string }{ {dns.RcodeSuccess, StatusNoData, ""}, {dns.RcodeNameError, StatusNXDomain, ""}, {dns.RcodeServerFailure, StatusError, "server returned SERVFAIL"}, {dns.RcodeNotImplemented, StatusError, "server returned NOTIMP"}, {dns.RcodeFormatError, StatusError, "server returned FORMERR"}, {12, StatusError, "server returned 12"}, // unassigned, no name } for _, tt := range tests { t.Run(strconv.Itoa(tt.rcode), func(t *testing.T) { t.Parallel() msg := new(dns.Msg) msg.Authoritative = true msg.Rcode = tt.rcode resp := &NameserverResponse{Records: map[string][]string{}} var state queryState err := readReply(msg, resp, &state) classifyResponse(resp, state) if tt.wantStatus == StatusError { require.ErrorIs(t, err, ErrUnusableReply) } else { require.NoError(t, err) } assert.Equal(t, tt.wantStatus, resp.Status) assert.Equal(t, tt.wantError, resp.Error) }) } }