package output import ( "bytes" "net" "strings" "testing" "gitea.hansenits.com.au/hits/ExploreDNS/internal/dns" "gitea.hansenits.com.au/hits/ExploreDNS/internal/traverse" miekgdns "github.com/miekg/dns" ) func TestTextFormatterProgressIndentation(t *testing.T) { root := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil) child := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 0.5, root) var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = false formatter := newTextFormatter(cfg, &buf) if err := formatter.WriteProgress(traverse.TraversalEvent{ Stage: traverse.EventStart, Result: traverse.TraversalResult{Referral: child}, }); err != nil { t.Fatalf("WriteProgress: %v", err) } out := buf.String() if !strings.HasPrefix(out, " 2 ") { t.Fatalf("expected depth-based indentation, got %q", out) } } func TestAttachHooksRespectsShowFlags(t *testing.T) { ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil) var progressCount int cfg := DefaultConfig() cfg.ShowProgress = false cfg.ShowResolves = false cfg.ShowAllStats = false var buf bytes.Buffer formatter := NewFormatter(cfg, &buf) hooks := AttachHooks(cfg, formatter) hooks.OnEvent(traverse.TraversalEvent{ Stage: traverse.EventStart, Result: traverse.TraversalResult{Referral: ref}, }) if buf.Len() != 0 { t.Fatalf("expected no output when ShowProgress is false") } cfg.ShowProgress = true hooks = AttachHooks(cfg, formatter) hooks.OnEvent(traverse.TraversalEvent{ Stage: traverse.EventStart, Result: traverse.TraversalResult{Referral: ref}, }) progressCount = strings.Count(buf.String(), "\n") if progressCount == 0 { t.Fatal("expected progress output when ShowProgress is true") } } func TestTextWriteResolve(t *testing.T) { ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil) server := net.ParseIP("198.41.0.4") resp := &traverse.Response{Server: server, Type: traverse.RespAnswer} var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = false f := newTextFormatter(cfg, &buf) // EventStart - should write line if err := f.WriteResolve(traverse.TraversalEvent{ Stage: traverse.EventStart, Result: traverse.TraversalResult{Referral: ref, Response: resp}, }); err != nil { t.Fatalf("WriteResolve EventStart: %v", err) } if buf.Len() == 0 { t.Error("expected output for WriteResolve EventStart") } buf.Reset() // EventComplete - should write nothing if err := f.WriteResolve(traverse.TraversalEvent{ Stage: traverse.EventComplete, Result: traverse.TraversalResult{Referral: ref, Response: resp}, }); err != nil { t.Fatalf("WriteResolve EventComplete: %v", err) } if buf.Len() != 0 { t.Error("expected no output for WriteResolve EventComplete") } } func TestTextWriteResult(t *testing.T) { ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil) server := net.ParseIP("198.41.0.4") tests := []struct { name string respType traverse.ResponseType msg *dns.DecodedResponse errorMsg string }{ {"answer", traverse.RespAnswer, &dns.DecodedResponse{ Answers: []miekgdns.RR{ &miekgdns.A{Hdr: miekgdns.RR_Header{Name: "example.com.", Rrtype: dns.TypeA, Class: miekgdns.ClassINET}, A: net.ParseIP("1.2.3.4")}, }, }, ""}, {"nodata", traverse.RespNODATA, nil, ""}, {"nxdomain", traverse.RespNXDOMAIN, nil, ""}, {"servfail", traverse.RespSERVFAIL, nil, ""}, {"refused", traverse.RespREFUSED, nil, ""}, {"notimp", traverse.RespNOTIMPL, nil, ""}, {"cname_loop", traverse.RespCNAMELoop, nil, "loop detected"}, {"ns_error", traverse.RespNSResolutionFailed, nil, "nameserver ns1.example.com could not be resolved"}, {"error", traverse.RespError, nil, "something went wrong"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = false f := newTextFormatter(cfg, &buf) resp := &traverse.Response{ Referral: ref, Server: server, Type: tc.respType, Decoded: tc.msg, ErrorMessage: tc.errorMsg, } result := traverse.TraversalResult{Referral: ref, Response: resp} if err := f.WriteResult(result); err != nil { t.Fatalf("WriteResult %q: %v", tc.name, err) } }) } } func TestTextWriteResultNilResponse(t *testing.T) { var buf bytes.Buffer cfg := DefaultConfig() f := newTextFormatter(cfg, &buf) if err := f.WriteResult(traverse.TraversalResult{Referral: nil, Response: nil}); err != nil { t.Fatalf("WriteResult nil: %v", err) } if buf.Len() != 0 { t.Error("expected no output for nil result") } } func TestTextWriteResultAnswerMultipleRRs(t *testing.T) { ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 0.5, 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: dns.TypeA, Class: miekgdns.ClassINET}, A: net.ParseIP("1.2.3.4")}, &miekgdns.A{Hdr: miekgdns.RR_Header{Name: "example.com.", Rrtype: dns.TypeA, Class: miekgdns.ClassINET}, A: net.ParseIP("5.6.7.8")}, }, }, } var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = false f := newTextFormatter(cfg, &buf) if err := f.WriteResult(traverse.TraversalResult{Referral: ref, Response: resp}); err != nil { t.Fatalf("WriteResult: %v", err) } if !strings.Contains(buf.String(), "/") { t.Errorf("expected '/' separator for multiple answers, got: %q", buf.String()) } } func TestTextWriteSummaryWithServersAndResults(t *testing.T) { ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 1.0, nil) server := net.ParseIP("1.2.3.4") resp := &traverse.Response{ Referral: ref, Server: server, Type: traverse.RespAnswer, Decoded: &dns.DecodedResponse{ Answers: []miekgdns.RR{ &miekgdns.A{Hdr: miekgdns.RR_Header{Name: "example.com.", Rrtype: dns.TypeA, Class: miekgdns.ClassINET}, A: net.ParseIP("1.2.3.4")}, }, }, } results := []traverse.TraversalResult{{Referral: ref, Response: resp}} var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = false cfg.ShowServers = true cfg.ShowResults = true cfg.ShowSummaryResults = true f := newTextFormatter(cfg, &buf) if err := f.WriteSummary(results); err != nil { t.Fatalf("WriteSummary: %v", err) } out := buf.String() if !strings.Contains(out, "Summary:") { t.Errorf("expected Summary: in output, got: %q", out) } if !strings.Contains(out, "Results:") { t.Errorf("expected Results: in output, got: %q", out) } } func TestTextWriteSummaryNoResults(t *testing.T) { var buf bytes.Buffer cfg := DefaultConfig() cfg.ShowServers = false cfg.ShowResults = false cfg.ShowSummaryResults = false f := newTextFormatter(cfg, &buf) if err := f.WriteSummary(nil); err != nil { t.Fatalf("WriteSummary nil: %v", err) } } func TestTextWriteSummaryNXDOMAIN(t *testing.T) { ref := traverse.NewReferral("gone.example.com.", dns.TypeA, "com.", 1, 1.0, nil) server := net.ParseIP("1.2.3.4") resp := &traverse.Response{ Referral: ref, Server: server, Type: traverse.RespNXDOMAIN, } results := []traverse.TraversalResult{{Referral: ref, Response: resp}} var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = false cfg.ShowServers = true cfg.ShowResults = true cfg.ShowSummaryResults = true f := newTextFormatter(cfg, &buf) if err := f.WriteSummary(results); err != nil { t.Fatalf("WriteSummary: %v", err) } } func TestFormatReferralLineVerbose(t *testing.T) { root := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil) child := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 1.0, root) var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = false cfg.Verbose = true f := newTextFormatter(cfg, &buf) event := traverse.TraversalEvent{ Stage: traverse.EventStart, Result: traverse.TraversalResult{Referral: child}, } if err := f.WriteProgress(event); err != nil { t.Fatalf("WriteProgress verbose: %v", err) } out := buf.String() if !strings.Contains(out, "com") { t.Errorf("expected bailiwick in verbose output, got: %q", out) } } func TestFormatReferralLineVerboseResolve(t *testing.T) { ref := traverse.NewReferral("example.com.", dns.TypeA, "com.", 1, 1.0, nil) server := net.ParseIP("1.2.3.4") resp := &traverse.Response{Server: server, Type: traverse.RespAnswer} var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = false cfg.Verbose = true f := newTextFormatter(cfg, &buf) if err := f.WriteResolve(traverse.TraversalEvent{ Stage: traverse.EventStart, Result: traverse.TraversalResult{Referral: ref, Response: resp}, }); err != nil { t.Fatalf("WriteResolve verbose: %v", err) } if buf.Len() == 0 { t.Error("expected output for verbose WriteResolve") } } func TestTextWriteProgressNoAddresses(t *testing.T) { // Test the "resolving" suffix when referral has no addresses ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil) // No addresses set, so HasAddresses() returns false var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = false f := newTextFormatter(cfg, &buf) if err := f.WriteProgress(traverse.TraversalEvent{ Stage: traverse.EventStart, Result: traverse.TraversalResult{Referral: ref}, }); err != nil { t.Fatalf("WriteProgress: %v", err) } if !strings.Contains(buf.String(), "resolving") { t.Errorf("expected 'resolving' suffix when no addresses, got: %q", buf.String()) } } func TestColorize(t *testing.T) { var buf bytes.Buffer cfg := DefaultConfig() cfg.Color = true f := newTextFormatter(cfg, &buf) colored := f.colorize("hello", colorGreen) if colored == "hello" { t.Error("expected colorized output with Color=true") } cfg.Color = false f2 := newTextFormatter(cfg, &buf) plain := f2.colorize("hello", colorGreen) if plain != "hello" { t.Errorf("expected plain text with Color=false, got %q", plain) } // Empty color empty := f.colorize("hello", "") if empty != "hello" { t.Errorf("expected plain text for empty color, got %q", empty) } } func TestReferralServerLabelFallbacks(t *testing.T) { // With server IP in response ref := traverse.NewReferral("example.com.", dns.TypeA, ".", 0, 1.0, nil) resp := &traverse.Response{Server: net.ParseIP("1.2.3.4")} label := referralServerLabel(ref, resp) if label != "1.2.3.4" { t.Errorf("expected '1.2.3.4', got %q", label) } // With addresses in referral, no response server ref2 := traverse.NewReferral("example.com.", dns.TypeA, "ns1.example.com.", 0, 1.0, nil) ref2.Addresses = []net.IP{net.ParseIP("5.6.7.8")} label2 := referralServerLabel(ref2, nil) if label2 != "5.6.7.8" { t.Errorf("expected '5.6.7.8', got %q", label2) } // With NSName ref3 := &traverse.Referral{ Name: "example.com.", NSName: "ns1.example.com.", Bailiwick: ".", } label3 := referralServerLabel(ref3, nil) if label3 != "ns1.example.com." { t.Errorf("expected NSName, got %q", label3) } // With non-root bailiwick, no addresses, no NSName ref4 := traverse.NewReferral("example.com.", dns.TypeA, "com.", 0, 1.0, nil) label4 := referralServerLabel(ref4, nil) if label4 != "com" { t.Errorf("expected 'com', got %q", label4) } }