Files
ExploreDNS/internal/output/coverage_test.go
T
5d4fa94286
CI / test (pull_request) Failing after 1m32s
CI / docker (pull_request) Has been skipped
fix: rename Go module path to gitea.hansenits.com.au/hits/ExploreDNS
- Update go.mod module declaration
- Update all internal import paths in .go files
- Update go install lines in README.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-08 23:17:00 +10:00

878 lines
25 KiB
Go

package output
import (
"bytes"
"context"
"net"
"strings"
"testing"
"gitea.hansenits.com.au/hits/ExploreDNS/internal/dns"
"gitea.hansenits.com.au/hits/ExploreDNS/internal/traverse"
miekgdns "github.com/miekg/dns"
)
// ---- stats.go coverage ----
func TestRRDataString(t *testing.T) {
cases := []struct {
rr miekgdns.RR
want string
}{
{
&miekgdns.A{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeA}, A: net.ParseIP("1.2.3.4")},
"1.2.3.4",
},
{
&miekgdns.AAAA{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeAAAA}, AAAA: net.ParseIP("::1")},
"::1",
},
{
&miekgdns.CNAME{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeCNAME}, Target: "example.com."},
"example.com.",
},
{
&miekgdns.NS{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeNS}, Ns: "ns1.example.com."},
"ns1.example.com.",
},
{
&miekgdns.MX{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeMX}, Preference: 10, Mx: "mail.example.com."},
"10 mail.example.com.",
},
{
&miekgdns.TXT{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeTXT}, Txt: []string{"v=spf1", "include:example.com"}},
"v=spf1 include:example.com",
},
}
for _, tc := range cases {
got := rrDataString(tc.rr)
if got != tc.want {
t.Errorf("rrDataString(%T) = %q, want %q", tc.rr, got, tc.want)
}
}
}
func TestSummaryTypeLabel(t *testing.T) {
cases := []struct {
input string
want string
}{
{"nodata", "found no such record"},
{"nxdomain", "name does not exist"},
{"servfail", "resulted in SERVFAIL"},
{"refused", "query refused by server"},
{"notimp", "query type not implemented by server"},
{"cname_loop", "resulted in a CNAME loop"},
{"error", "resulted in an error"},
{"referral", "resulted in a referral"},
{"unknown_type", "unknown_type"},
}
for _, tc := range cases {
got := summaryTypeLabel(tc.input)
if got != tc.want {
t.Errorf("summaryTypeLabel(%q) = %q, want %q", tc.input, got, tc.want)
}
}
}
func TestCollectServers(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 0.5, nil)
resp := &traverse.Response{
Referral: ref,
Server: net.ParseIP("1.2.3.4"),
Type: traverse.RespAnswer,
}
results := []traverse.TraversalResult{
{Referral: ref, Response: resp},
}
servers := collectServers(results)
if len(servers) == 0 {
t.Fatal("expected at least one server")
}
}
func TestCollectServersNilServer(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Server: nil,
Type: traverse.RespAnswer,
}
results := []traverse.TraversalResult{{Referral: ref, Response: resp}}
servers := collectServers(results)
if len(servers) != 0 {
t.Errorf("expected 0 servers with nil server, got %d", len(servers))
}
}
func TestCollectServersDedup(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 0.5, nil)
resp := &traverse.Response{
Referral: ref,
Server: net.ParseIP("1.2.3.4"),
Type: traverse.RespAnswer,
}
results := []traverse.TraversalResult{
{Referral: ref, Response: resp},
{Referral: ref, Response: resp},
}
servers := collectServers(results)
for _, ips := range servers {
for _, ip := range ips {
count := 0
for _, i := range ips {
if i == ip {
count++
}
}
if count > 1 {
t.Errorf("duplicate IP %s in server list", ip)
}
}
}
}
func TestServerName(t *testing.T) {
t.Run("uses bailiwick", func(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 1.0, nil)
result := traverse.TraversalResult{Referral: ref, Response: nil}
name := serverName(result)
if name != "com" {
t.Errorf("serverName = %q, want 'com'", name)
}
})
t.Run("uses NSName when bailiwick is root", func(t *testing.T) {
ref := traverse.NewReferral("ns1.example.com.", dns.TypeA, ".", 0, 1.0, nil)
ref.NSName = "ns1.example.com."
result := traverse.TraversalResult{Referral: ref, Response: nil}
name := serverName(result)
if name != "ns1.example.com." {
t.Errorf("serverName = %q, want 'ns1.example.com.'", name)
}
})
t.Run("uses server IP from response", func(t *testing.T) {
ref := traverse.NewReferral("ns1.example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Server: net.ParseIP("1.2.3.4"),
}
result := traverse.TraversalResult{Referral: ref, Response: resp}
name := serverName(result)
if name != "1.2.3.4" {
t.Errorf("serverName = %q, want '1.2.3.4'", name)
}
})
t.Run("unknown fallback", func(t *testing.T) {
result := traverse.TraversalResult{Referral: nil, Response: nil}
name := serverName(result)
if name != "unknown" {
t.Errorf("serverName = %q, want 'unknown'", name)
}
})
}
func TestComputeSummaryNonAnswerTypes(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
for _, respType := range []traverse.ResponseType{
traverse.RespNXDOMAIN, traverse.RespSERVFAIL, traverse.RespNODATA,
} {
resp := &traverse.Response{Referral: ref, Type: respType}
stats := ComputeSummary([]traverse.TraversalResult{{Referral: ref, Response: resp}})
if stats == nil {
t.Errorf("ComputeSummary returned nil for %v", respType)
continue
}
if len(stats.ByType) == 0 {
t.Errorf("expected ByType entry for %v", respType)
}
}
}
func TestComputeSummaryNilResponse(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
stats := ComputeSummary([]traverse.TraversalResult{{Referral: ref, Response: nil}})
if stats != nil {
t.Error("expected nil stats for nil response")
}
}
func TestComputeSummaryNilReferral(t *testing.T) {
resp := &traverse.Response{Type: traverse.RespAnswer}
stats := ComputeSummary([]traverse.TraversalResult{{Referral: nil, Response: resp}})
if stats != nil {
t.Error("expected nil stats for nil referral")
}
}
func TestComputeSummaryAnswerKeyEmpty(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
// Answer with only CNAME (no data key) should go into ByType
resp := &traverse.Response{
Referral: ref,
Type: traverse.RespAnswer,
Decoded: &dns.DecodedResponse{
Answers: []miekgdns.RR{
&miekgdns.CNAME{
Hdr: miekgdns.RR_Header{Name: "www.example.com.", Rrtype: miekgdns.TypeCNAME},
Target: "example.com.",
},
},
},
}
stats := ComputeSummary([]traverse.TraversalResult{{Referral: ref, Response: resp}})
if stats == nil {
t.Fatal("expected non-nil stats")
}
}
// ---- text.go coverage ----
func TestTextFormatterWriteResolve(t *testing.T) {
ref := traverse.NewReferral("ns1.example.com.", dns.TypeA, "example.com.", 1, 0.5, nil)
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Color = false
f := newTextFormatter(cfg, &buf)
err := f.WriteResolve(traverse.TraversalEvent{
Stage: traverse.EventStart,
Result: traverse.TraversalResult{Referral: ref},
IsResolve: true,
})
if err != nil {
t.Fatalf("WriteResolve: %v", err)
}
if buf.Len() == 0 {
t.Error("expected output from WriteResolve")
}
}
func TestTextFormatterWriteResolveNonStart(t *testing.T) {
ref := traverse.NewReferral("ns1.example.com.", dns.TypeA, "example.com.", 1, 0.5, nil)
var buf bytes.Buffer
cfg := DefaultConfig()
f := newTextFormatter(cfg, &buf)
err := f.WriteResolve(traverse.TraversalEvent{
Stage: traverse.EventComplete,
Result: traverse.TraversalResult{Referral: ref},
IsResolve: true,
})
if err != nil {
t.Fatalf("WriteResolve: %v", err)
}
if buf.Len() != 0 {
t.Error("expected no output for non-start resolve event")
}
}
func TestTextFormatterWriteServers(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Server: net.ParseIP("1.2.3.4"),
Type: traverse.RespAnswer,
Decoded: &dns.DecodedResponse{
Answers: []miekgdns.RR{
&miekgdns.A{
Hdr: miekgdns.RR_Header{Name: "example.com.", Rrtype: miekgdns.TypeA},
A: net.ParseIP("1.2.3.4"),
},
},
},
}
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Color = false
cfg.ShowServers = true
cfg.ShowResults = false
cfg.ShowSummaryResults = false
f := newTextFormatter(cfg, &buf)
err := f.WriteSummary([]traverse.TraversalResult{{Referral: ref, Response: resp}})
if err != nil {
t.Fatalf("WriteSummary: %v", err)
}
if !strings.Contains(buf.String(), "The following servers were encountered:") {
t.Errorf("expected server list header, got %q", buf.String())
}
}
func TestTextFormatterWriteResults(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Server: net.ParseIP("1.2.3.4"),
Type: traverse.RespAnswer,
Decoded: &dns.DecodedResponse{
Answers: []miekgdns.RR{
&miekgdns.A{
Hdr: miekgdns.RR_Header{Name: "example.com.", Rrtype: miekgdns.TypeA},
A: net.ParseIP("93.184.216.34"),
},
},
},
}
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Color = false
cfg.ShowServers = false
cfg.ShowResults = true
cfg.ShowSummaryResults = false
f := newTextFormatter(cfg, &buf)
err := f.WriteSummary([]traverse.TraversalResult{{Referral: ref, Response: resp}})
if err != nil {
t.Fatalf("WriteSummary: %v", err)
}
if !strings.Contains(buf.String(), "Results:") {
t.Errorf("expected 'Results:' header, got %q", buf.String())
}
}
func TestTextFormatterFormatResultLineAllTypes(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
cases := []struct {
respType traverse.ResponseType
contains string
}{
{traverse.RespNODATA, "no such record"},
{traverse.RespNXDOMAIN, "does not exist"},
{traverse.RespSERVFAIL, "SERVFAIL"},
{traverse.RespREFUSED, "refused"},
{traverse.RespNOTIMPL, "not implemented"},
{traverse.RespCNAMELoop, "CNAME loop"},
{traverse.RespError, "error"},
}
cfg := DefaultConfig()
cfg.Color = false
f := newTextFormatter(cfg, &bytes.Buffer{})
for _, tc := range cases {
resp := &traverse.Response{
Referral: ref,
Type: tc.respType,
}
result := traverse.TraversalResult{Referral: ref, Response: resp}
line := f.formatResultLine(result)
if !strings.Contains(strings.ToLower(line), strings.ToLower(tc.contains)) {
t.Errorf("formatResultLine(%v) = %q, want substring %q", tc.respType, line, tc.contains)
}
}
}
func TestTextFormatterFormatResultLineErrorWithMessage(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Type: traverse.RespError,
ErrorMessage: "custom error message",
}
cfg := DefaultConfig()
cfg.Color = false
f := newTextFormatter(cfg, &bytes.Buffer{})
line := f.formatResultLine(traverse.TraversalResult{Referral: ref, Response: resp})
if !strings.Contains(line, "custom error message") {
t.Errorf("expected custom error message, got %q", line)
}
}
func TestTextFormatterFormatResultLineCNAMELoopWithMessage(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Type: traverse.RespCNAMELoop,
ErrorMessage: "CNAME loop detected: example.com",
}
cfg := DefaultConfig()
cfg.Color = false
f := newTextFormatter(cfg, &bytes.Buffer{})
line := f.formatResultLine(traverse.TraversalResult{Referral: ref, Response: resp})
if !strings.Contains(line, "CNAME loop detected") {
t.Errorf("expected CNAME loop message, got %q", line)
}
}
func TestTextFormatterFormatResultLineAnswerMultiple(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Type: traverse.RespAnswer,
Decoded: &dns.DecodedResponse{
Answers: []miekgdns.RR{
&miekgdns.A{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeA}, A: net.ParseIP("1.1.1.1")},
&miekgdns.A{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeA}, A: net.ParseIP("2.2.2.2")},
},
},
}
cfg := DefaultConfig()
cfg.Color = false
f := newTextFormatter(cfg, &bytes.Buffer{})
line := f.formatResultLine(traverse.TraversalResult{Referral: ref, Response: resp})
if !strings.Contains(line, "/") {
t.Errorf("expected '/' separator for multiple answers, got %q", line)
}
}
func TestTextFormatterColorize(t *testing.T) {
cfg := DefaultConfig()
cfg.Color = true
f := newTextFormatter(cfg, &bytes.Buffer{})
colored := f.colorize("hello", colorGreen)
if !strings.Contains(colored, "\033[") {
t.Error("expected ANSI color code in colored output")
}
cfg.Color = false
f2 := newTextFormatter(cfg, &bytes.Buffer{})
plain := f2.colorize("hello", colorGreen)
if plain != "hello" {
t.Errorf("expected plain text without color, got %q", plain)
}
}
func TestTextFormatterColorizeEmpty(t *testing.T) {
cfg := DefaultConfig()
cfg.Color = true
f := newTextFormatter(cfg, &bytes.Buffer{})
out := f.colorize("hello", "")
if out != "hello" {
t.Errorf("empty color should return plain text, got %q", out)
}
}
func TestTextFormatterVerboseProgress(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 0.5, nil)
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Color = false
cfg.Verbose = true
f := newTextFormatter(cfg, &buf)
err := f.WriteProgress(traverse.TraversalEvent{
Stage: traverse.EventStart,
Result: traverse.TraversalResult{Referral: ref},
})
if err != nil {
t.Fatalf("WriteProgress: %v", err)
}
if buf.Len() == 0 {
t.Error("expected output with verbose mode")
}
out := buf.String()
if !strings.Contains(out, "com") {
t.Errorf("expected bailiwick in verbose output, got %q", out)
}
}
func TestTextFormatterProgressResolving(t *testing.T) {
ref := traverse.NewReferral("ns1.example.com.", dns.TypeA, "example.com.", 1, 0.5, nil)
// no addresses = resolving
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Color = false
f := newTextFormatter(cfg, &buf)
err := f.WriteProgress(traverse.TraversalEvent{
Stage: traverse.EventStart,
Result: traverse.TraversalResult{Referral: ref},
})
if err != nil {
t.Fatalf("WriteProgress: %v", err)
}
if !strings.Contains(buf.String(), "resolving") {
t.Errorf("expected 'resolving' in output, got %q", buf.String())
}
}
func TestTextFormatterWriteServersWithVersions(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Server: net.ParseIP("1.2.3.4"),
Type: traverse.RespAnswer,
Decoded: &dns.DecodedResponse{
Answers: []miekgdns.RR{
&miekgdns.A{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeA}, A: net.ParseIP("1.2.3.4")},
},
},
}
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Color = false
cfg.ShowServers = true
cfg.ShowResults = false
cfg.ShowSummaryResults = false
cfg.ShowVersions = true
cfg.Fingerprints = map[string]string{"1.2.3.4": "BIND 9.16"}
f := newTextFormatter(cfg, &buf)
err := f.WriteSummary([]traverse.TraversalResult{{Referral: ref, Response: resp}})
if err != nil {
t.Fatalf("WriteSummary: %v", err)
}
if !strings.Contains(buf.String(), "BIND 9.16") {
t.Errorf("expected version string in server output, got %q", buf.String())
}
}
func TestTextFormatterWriteResult(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Type: traverse.RespAnswer,
Decoded: &dns.DecodedResponse{
Answers: []miekgdns.RR{
&miekgdns.A{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeA}, A: net.ParseIP("1.2.3.4")},
},
},
}
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Color = false
f := newTextFormatter(cfg, &buf)
err := f.WriteResult(traverse.TraversalResult{Referral: ref, Response: resp})
if err != nil {
t.Fatalf("WriteResult: %v", err)
}
if buf.Len() == 0 {
t.Error("expected output from WriteResult")
}
}
func TestTextFormatterWriteResultNilRefs(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
f := newTextFormatter(cfg, &buf)
err := f.WriteResult(traverse.TraversalResult{Referral: nil, Response: nil})
if err != nil {
t.Fatalf("WriteResult: %v", err)
}
if buf.Len() != 0 {
t.Error("expected no output for nil referral/response")
}
}
func TestReferralServerLabelVariants(t *testing.T) {
t.Run("with addresses", func(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
ref.Addresses = []net.IP{net.ParseIP("1.2.3.4")}
label := referralServerLabel(ref, nil)
if !strings.Contains(label, "1.2.3.4") {
t.Errorf("expected IP in label, got %q", label)
}
})
t.Run("with NSName", func(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
ref.NSName = "ns1.example.com."
label := referralServerLabel(ref, nil)
if label != "ns1.example.com." {
t.Errorf("expected NSName, got %q", label)
}
})
t.Run("with bailiwick", func(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 1.0, nil)
label := referralServerLabel(ref, nil)
if label != "com" {
t.Errorf("expected trimmed bailiwick, got %q", label)
}
})
t.Run("unknown", func(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
label := referralServerLabel(ref, nil)
if label != "unknown" {
t.Errorf("expected 'unknown', got %q", label)
}
})
t.Run("with response server", func(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{Server: net.ParseIP("5.6.7.8")}
label := referralServerLabel(ref, resp)
if label != "5.6.7.8" {
t.Errorf("expected server IP, got %q", label)
}
})
}
// ---- json.go coverage ----
func TestJSONFormatterWriteResolve(t *testing.T) {
ref := traverse.NewReferral("ns1.example.com.", dns.TypeA, "example.com.", 1, 0.5, nil)
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
cfg.ShowResolves = true
f := newJSONFormatter(cfg, &buf)
err := f.WriteResolve(traverse.TraversalEvent{
Stage: traverse.EventStart,
Result: traverse.TraversalResult{Referral: ref},
IsResolve: true,
})
if err != nil {
t.Fatalf("WriteResolve: %v", err)
}
if len(f.payload.Resolves) != 1 {
t.Errorf("expected 1 resolve entry, got %d", len(f.payload.Resolves))
}
}
func TestJSONFormatterWriteResolveShowResolvesFalse(t *testing.T) {
ref := traverse.NewReferral("ns1.example.com.", dns.TypeA, "example.com.", 1, 0.5, nil)
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
cfg.ShowResolves = false
f := newJSONFormatter(cfg, &buf)
err := f.WriteResolve(traverse.TraversalEvent{
Stage: traverse.EventStart,
Result: traverse.TraversalResult{Referral: ref},
IsResolve: true,
})
if err != nil {
t.Fatalf("WriteResolve: %v", err)
}
if len(f.payload.Resolves) != 0 {
t.Errorf("expected 0 resolve entries when ShowResolves=false, got %d", len(f.payload.Resolves))
}
}
func TestJSONFormatterWriteResult(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Server: net.ParseIP("1.2.3.4"),
Type: traverse.RespAnswer,
Decoded: &dns.DecodedResponse{
Answers: []miekgdns.RR{
&miekgdns.A{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeA}, A: net.ParseIP("1.2.3.4")},
},
},
}
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
cfg.ShowAllStats = true
f := newJSONFormatter(cfg, &buf)
err := f.WriteResult(traverse.TraversalResult{Referral: ref, Response: resp})
if err != nil {
t.Fatalf("WriteResult: %v", err)
}
if len(f.payload.Results) != 1 {
t.Errorf("expected 1 result entry, got %d", len(f.payload.Results))
}
}
func TestJSONFormatterWriteResultShowAllStatsFalse(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{Referral: ref, Type: traverse.RespAnswer}
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
cfg.ShowAllStats = false
f := newJSONFormatter(cfg, &buf)
err := f.WriteResult(traverse.TraversalResult{Referral: ref, Response: resp})
if err != nil {
t.Fatalf("WriteResult: %v", err)
}
if len(f.payload.Results) != 0 {
t.Errorf("expected 0 result entries when ShowAllStats=false, got %d", len(f.payload.Results))
}
}
func TestJSONFormatterWriteSummaryWithServersAndVersions(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Server: net.ParseIP("1.2.3.4"),
Type: traverse.RespAnswer,
Decoded: &dns.DecodedResponse{
Answers: []miekgdns.RR{
&miekgdns.A{Hdr: miekgdns.RR_Header{Rrtype: miekgdns.TypeA}, A: net.ParseIP("1.2.3.4")},
},
},
}
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
cfg.ShowServers = true
cfg.ShowVersions = true
cfg.Fingerprints = map[string]string{"1.2.3.4": "BIND 9.16"}
f := newJSONFormatter(cfg, &buf)
err := f.WriteSummary([]traverse.TraversalResult{{Referral: ref, Response: resp}})
if err != nil {
t.Fatalf("WriteSummary: %v", err)
}
found := false
for _, srv := range f.payload.Servers {
if srv.Version == "BIND 9.16" {
found = true
}
}
if !found {
t.Error("expected version in server list")
}
}
func TestJSONFormatterStageName(t *testing.T) {
if stageName(traverse.EventStart) != "start" {
t.Errorf("expected 'start', got %q", stageName(traverse.EventStart))
}
if stageName(traverse.EventComplete) != "complete" {
t.Errorf("expected 'complete', got %q", stageName(traverse.EventComplete))
}
if stageName(traverse.EventStage(99)) != "unknown" {
t.Errorf("expected 'unknown' for unknown stage")
}
}
func TestJSONFormatterEventToJSONNilReferral(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
f := newJSONFormatter(cfg, &buf)
item := f.eventToJSON(traverse.TraversalEvent{
Stage: traverse.EventStart,
Result: traverse.TraversalResult{Referral: nil},
})
if item.Name != "" {
t.Errorf("expected empty name for nil referral, got %q", item.Name)
}
}
func TestJSONFormatterEventToJSONWithResponse(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
resp := &traverse.Response{
Referral: ref,
Server: net.ParseIP("1.2.3.4"),
}
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
f := newJSONFormatter(cfg, &buf)
item := f.eventToJSON(traverse.TraversalEvent{
Stage: traverse.EventStart,
Result: traverse.TraversalResult{Referral: ref, Response: resp},
})
if item.Server != "1.2.3.4" {
t.Errorf("expected server IP, got %q", item.Server)
}
}
func TestJSONFormatterWriteProgressShowProgressFalse(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
cfg.ShowProgress = false
f := newJSONFormatter(cfg, &buf)
err := f.WriteProgress(traverse.TraversalEvent{
Stage: traverse.EventStart,
Result: traverse.TraversalResult{Referral: ref},
})
if err != nil {
t.Fatalf("WriteProgress: %v", err)
}
if len(f.payload.Progress) != 0 {
t.Errorf("expected 0 progress entries when ShowProgress=false, got %d", len(f.payload.Progress))
}
}
// ---- runner.go coverage ----
func TestRunTraversalNilTraverser(t *testing.T) {
_, err := RunTraversal(context.Background(), nil, nil, nil, "example.com")
if err == nil {
t.Fatal("expected error for nil traverser")
}
}
func TestNewFormatterNilConfig(t *testing.T) {
f := NewFormatter(nil, &bytes.Buffer{})
if f == nil {
t.Fatal("NewFormatter(nil) should not return nil")
}
}
func TestAttachHooksNilCfg(t *testing.T) {
h := AttachHooks(nil, nil)
if h != nil {
t.Fatal("AttachHooks(nil, nil) should return nil")
}
}
func TestAttachHooksDebugMode(t *testing.T) {
ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil)
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Debug = 1
cfg.ShowResolves = true
// formatter that returns an error on WriteResolve
formatter := &errorFormatter{}
hooks := AttachHooks(cfg, formatter)
if hooks == nil {
t.Fatal("expected non-nil hooks")
}
// Call OnEvent with IsResolve=true - should call WriteResolve and log error to stderr (debug>0)
hooks.OnEvent(traverse.TraversalEvent{
Stage: traverse.EventStart,
Result: traverse.TraversalResult{Referral: ref},
IsResolve: true,
})
_ = buf.String() // no assertion - just ensure it doesn't panic
}
// errorFormatter is a mock formatter for testing error paths.
type errorFormatter struct{}
func (f *errorFormatter) WriteProgress(_ traverse.TraversalEvent) error { return nil }
func (f *errorFormatter) WriteResolve(_ traverse.TraversalEvent) error { return nil }
func (f *errorFormatter) WriteResult(_ traverse.TraversalResult) error { return nil }
func (f *errorFormatter) WriteSummary(_ []traverse.TraversalResult) error {
return nil
}
func (f *errorFormatter) Flush() error { return nil }