// SPDX-License-Identifier: MIT package checker import ( "reflect" "sort" "strings" "testing" ) func TestLabelDepth(t *testing.T) { cases := map[string]int{ "": 0, ".": 0, "com.": 1, "example.com.": 2, "www.example.com": 3, "a.b.c.d.e.": 5, } for in, want := range cases { if got := labelDepth(in); got != want { t.Errorf("labelDepth(%q) = %d, want %d", in, got, want) } } } func TestJoinPath(t *testing.T) { cases := []struct { parent, key, want string }{ {"", "errors", "errors"}, {"delegation", "errors", "delegation/errors"}, {"queries/example.com.", "answer", "queries/example.com./answer"}, } for _, c := range cases { if got := joinPath(c.parent, c.key); got != c.want { t.Errorf("joinPath(%q,%q) = %q, want %q", c.parent, c.key, got, c.want) } } } func TestParseGrokOutput_OrderAndShape(t *testing.T) { raw := []byte(`{ "example.com.": { "status": "NOERROR", "delegation": {"status": "SECURE"}, "queries": {"example.com./A": {"errors": [{"code": "X", "description": "boom"}]}} }, "com.": {"delegation": {"status": "SECURE"}}, ".": {"delegation": {"status": "SECURE"}}, "_meta": {"ignored": true} }`) zones, order, err := ParseGrokOutput(raw) if err != nil { t.Fatalf("ParseGrokOutput: %v", err) } if _, ok := zones["_meta"]; ok { t.Errorf("expected _meta-prefixed key to be skipped, got it in zones") } if len(zones) != 3 { t.Errorf("expected 3 zones, got %d (%v)", len(zones), zones) } // Order: most-specific first (example.com.), root last. if !reflect.DeepEqual(order, []string{"example.com.", "com.", "."}) { t.Errorf("unexpected order: %v", order) } if zones["example.com."].Status != "SECURE" { t.Errorf("expected delegation.status to win for example.com., got %q", zones["example.com."].Status) } if zones["example.com."].DNSStatus != "NOERROR" { t.Errorf("expected DNSStatus=NOERROR, got %q", zones["example.com."].DNSStatus) } if len(zones["example.com."].Errors) != 1 { t.Fatalf("expected 1 error, got %v", zones["example.com."].Errors) } if zones["example.com."].Errors[0].Code != "X" { t.Errorf("expected code=X, got %q", zones["example.com."].Errors[0].Code) } } func TestParseGrokOutput_InvalidJSON(t *testing.T) { if _, _, err := ParseGrokOutput([]byte("not json")); err == nil { t.Fatal("expected error for invalid JSON") } } func TestParseGrokOutput_StringZone(t *testing.T) { // Old grok: a zone may collapse into a bare string status. raw := []byte(`{"missing.example.": "NON_EXISTENT"}`) zones, _, err := ParseGrokOutput(raw) if err != nil { t.Fatalf("ParseGrokOutput: %v", err) } if zones["missing.example."].Status != "NON_EXISTENT" { t.Errorf("got %q, want NON_EXISTENT", zones["missing.example."].Status) } } func TestDecodeZone_StatusFallbacks(t *testing.T) { // Only top-level status; no delegation block. Status must fall back to it. raw := []byte(`{"status": "NOERROR"}`) z := decodeZone(raw) if z.DNSStatus != "NOERROR" || z.Status != "NOERROR" { t.Errorf("expected Status and DNSStatus = NOERROR, got %+v", z) } } func TestCollectFindings_Nested(t *testing.T) { raw := []byte(`{ "delegation": { "errors": [{"code": "DS", "description": "missing"}] }, "queries": { "example.com./A": { "answer": [ {"warnings": [{"code": "W1", "description": "smelly"}]} ] } } }`) z := decodeZone(raw) if len(z.Errors) != 1 || z.Errors[0].Path != "delegation" { t.Errorf("expected one error tagged delegation, got %+v", z.Errors) } if len(z.Warnings) != 1 { t.Fatalf("expected one warning, got %+v", z.Warnings) } w := z.Warnings[0] if !strings.HasPrefix(w.Path, "queries/example.com./A/answer[") { t.Errorf("unexpected warning path: %q", w.Path) } if w.Code != "W1" || w.Description != "smelly" { t.Errorf("unexpected warning content: %+v", w) } } func TestAsFindings_VariantShapes(t *testing.T) { // Object-keyed-by-code variant. out := asFindings(map[string]any{ "CODE_B": map[string]any{"description": "second"}, "CODE_A": map[string]any{"description": "first"}, }, "p") if len(out) != 2 { t.Fatalf("expected 2 findings, got %v", out) } // Sorted by key for stability. if out[0].Code != "CODE_A" || out[1].Code != "CODE_B" { t.Errorf("findings not sorted by key: %+v", out) } for _, f := range out { if f.Path != "p" { t.Errorf("expected path=p, got %q", f.Path) } } // []string variant (rare but supported via direct call). strs := asFindings([]string{"raw1", "raw2"}, "p") if len(strs) != 2 || strs[0].Description != "raw1" { t.Errorf("string-list shape mishandled: %+v", strs) } // Unsupported scalar shape returns nil. if asFindings(42, "p") != nil { t.Errorf("expected nil for non-list non-map non-string-slice") } } func TestMakeFinding_FallbackAndServers(t *testing.T) { // description missing, message present. f := makeFinding(map[string]any{ "message": "use-message", "servers": []any{"ns1.example.", "ns2.example.", 42 /*ignored*/}, }, "fallback_code", "p") if f.Description != "use-message" { t.Errorf("wanted message fallback, got %q", f.Description) } if !reflect.DeepEqual(f.Servers, []string{"ns1.example.", "ns2.example."}) { t.Errorf("non-string server entries should be skipped, got %v", f.Servers) } if f.Code != "fallback_code" { t.Errorf("expected codeHint to be used when item has no code, got %q", f.Code) } // neither description nor message: keep the raw payload in Extra // instead of synthesising a JSON blob into Description (which would // then render as ugly text in the report). f2 := makeFinding(map[string]any{"weird": 1}, "", "p") if f2.Description != "" { t.Errorf("expected empty Description when no human text available, got %q", f2.Description) } if f2.Extra == nil || f2.Extra["weird"] != 1 { t.Errorf("expected raw payload in Extra, got %+v", f2.Extra) } // Plain string item. f3 := makeFinding("just a string", "h", "p") if f3.Description != "just a string" || f3.Code != "h" { t.Errorf("string item mishandled: %+v", f3) } // Item explicit code overrides codeHint. f4 := makeFinding(map[string]any{"code": "REAL", "description": "d"}, "hint", "p") if f4.Code != "REAL" { t.Errorf("expected explicit code to win, got %q", f4.Code) } } func TestParseGrokOutput_OrderStable(t *testing.T) { // Same-depth zones should still produce a deterministic slice (keys order // in Go maps is randomized) - just checks the zones each appear once. raw := []byte(`{"a.": {}, "b.": {}}`) _, order, err := ParseGrokOutput(raw) if err != nil { t.Fatal(err) } cp := append([]string(nil), order...) sort.Strings(cp) if !reflect.DeepEqual(cp, []string{"a.", "b."}) { t.Errorf("missing zones in order: %v", order) } }