Files
ExploreDNS/internal/config/config_test.go
T
Gary HansenandClaude Fable 5 d71c7fbef2 feat: rework engine and CLI for dnstraverse parity
Port the traversal engine to the Ruby dnstraverse model so behaviour and
output match dns.squish.net:

- dns: single RD=0 query path (RD=1 only for upstream root discovery),
  per-run packet cache, EDNS0 512-fallback with warnings, UDP->TCP on
  truncation; fix --retries 0 and --root-server IP-literal handling;
  drop all hardcoded 127.0.0.1:53 resolvers
- traverse: hierarchical per-branch InfoCache, 7-step response
  classification with the full 10-status vocabulary, bailiwick
  partitioning, strictly-deeper lame-referral rule, refid grammar with
  .0 resolve subtrees and childset digits, per-IP branching at 1/n
  weight, cache-based glue resolution with noglue/loop dead ends, CNAME
  restarts from the deepest cached zone, fast-mode memoization,
  probability aggregation with Ruby-identical stats keys (sums to 1.0)
- output: byte-for-byte reference text format pinned by a golden test,
  reference CLI defaults, working --quiet/--show-X=false, TTY-aware
  colour, deduplicated deterministic JSON
- web: adapt API/SPA to the new engine, SSE events carry refid/status,
  fix subscribe/snapshot duplicate-event race and a statusCls TDZ bug,
  align SPA type list with the backend
- delete the old engine and dead code (net -4,350 lines)

Verified against live runs of the reference Ruby engine across five
domains (answers, NXDOMAIN, null MX, CNAME restart, glueless resolve)
with no divergences beyond the documented typo fixes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 21:42:06 +10:00

168 lines
4.0 KiB
Go

package config
import (
"errors"
"testing"
)
func TestParseQueryTypeValid(t *testing.T) {
cases := []struct {
input string
wantType uint16
}{
{"A", 1},
{"aaaa", 28},
{"NS", 2},
{"CNAME", 5},
{"MX", 15},
{"TXT", 16},
{"SOA", 6},
{"PTR", 12},
{"ANY", 255},
}
for _, tc := range cases {
got, err := ParseQueryType(tc.input)
if err != nil {
t.Errorf("ParseQueryType(%q) unexpected error: %v", tc.input, err)
}
if got != tc.wantType {
t.Errorf("ParseQueryType(%q) = %d, want %d", tc.input, got, tc.wantType)
}
}
}
func TestParseQueryTypeInvalid(t *testing.T) {
_, err := ParseQueryType("BOGUS")
if err == nil {
t.Fatal("expected error for invalid query type")
}
if !errors.Is(err, ErrInvalidQueryType) {
t.Errorf("expected ErrInvalidQueryType, got %v", err)
}
}
func TestValidateOK(t *testing.T) {
cfg := DefaultConfig()
if err := cfg.Validate(); err != nil {
t.Fatalf("DefaultConfig should be valid, got: %v", err)
}
}
func TestValidateAlwaysTCPRequiresAllowTCP(t *testing.T) {
cfg := DefaultConfig()
cfg.AlwaysTCP = true
cfg.AllowTCP = false
err := cfg.Validate()
if err == nil {
t.Fatal("expected error when AlwaysTCP=true and AllowTCP=false")
}
if !errors.Is(err, ErrAlwaysTCPRequiresTCP) {
t.Errorf("expected ErrAlwaysTCPRequiresTCP, got %v", err)
}
}
func TestValidateAlwaysTCPWithAllowTCP(t *testing.T) {
cfg := DefaultConfig()
cfg.AlwaysTCP = true
cfg.AllowTCP = true
if err := cfg.Validate(); err != nil {
t.Errorf("AlwaysTCP=true, AllowTCP=true should be valid, got: %v", err)
}
}
func TestValidateBadUDPSize(t *testing.T) {
cfg := DefaultConfig()
cfg.UDPSize = 100
if err := cfg.Validate(); !errors.Is(err, ErrInvalidUDPSize) {
t.Errorf("expected ErrInvalidUDPSize, got %v", err)
}
}
func TestValidateBadMaxDepth(t *testing.T) {
cfg := DefaultConfig()
cfg.MaxDepth = 0
if err := cfg.Validate(); !errors.Is(err, ErrInvalidMaxDepth) {
t.Errorf("expected ErrInvalidMaxDepth, got %v", err)
}
}
func TestValidateBadRetries(t *testing.T) {
cfg := DefaultConfig()
cfg.Retries = 11
if err := cfg.Validate(); !errors.Is(err, ErrInvalidRetries) {
t.Errorf("expected ErrInvalidRetries, got %v", err)
}
}
func TestGetDomainOK(t *testing.T) {
cfg := DefaultConfig()
d, err := cfg.GetDomain([]string{"example.com"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if d != "example.com" {
t.Errorf("got %q, want %q", d, "example.com")
}
}
func TestGetDomainMissing(t *testing.T) {
cfg := DefaultConfig()
_, err := cfg.GetDomain([]string{})
if err == nil {
t.Fatal("expected error for missing domain")
}
if !errors.Is(err, ErrMissingDomain) {
t.Errorf("expected ErrMissingDomain, got %v", err)
}
}
func TestParseDebugLevel(t *testing.T) {
cases := []struct {
d, dd bool
want int
}{
{false, false, 0},
{true, false, 1},
{false, true, 2},
{true, true, 2}, // dd takes precedence
}
for _, tc := range cases {
got := ParseDebugLevel(tc.d, tc.dd)
if got != tc.want {
t.Errorf("ParseDebugLevel(d=%v, dd=%v) = %d, want %d", tc.d, tc.dd, got, tc.want)
}
}
}
func TestPrintUsage(t *testing.T) {
// PrintUsage writes to stderr; just ensure it doesn't panic.
PrintUsage()
}
func TestValidateBadQueryType(t *testing.T) {
cfg := DefaultConfig()
cfg.QueryType = "BOGUS"
if err := cfg.Validate(); !errors.Is(err, ErrInvalidQueryType) {
t.Errorf("expected ErrInvalidQueryType, got %v", err)
}
}
func TestDefaultConfigIsValid(t *testing.T) {
cfg := DefaultConfig()
if cfg.QueryType != "a" {
t.Errorf("QueryType = %q, want a", cfg.QueryType)
}
if cfg.ShowResolves || cfg.ShowServers || cfg.ShowAllStats {
t.Error("show-resolves/show-servers/show-all-stats must default to false")
}
if !cfg.ShowProgress || !cfg.ShowVersions || !cfg.ShowResults || !cfg.ShowSummaryResults {
t.Error("show-progress/show-versions/show-results/show-summary-results must default to true")
}
if cfg.MaxDepth != 20 {
t.Errorf("MaxDepth = %d, want 20", cfg.MaxDepth)
}
if cfg.Retries != 2 {
t.Errorf("Retries = %d, want 2", cfg.Retries)
}
}