package checker import ( "encoding/json" "testing" sdk "git.happydns.org/checker-sdk-go/checker" ) func TestProvider(t *testing.T) { p := Provider() if p == nil { t.Fatal("Provider() returned nil") } if p.Key() != ObservationKey { t.Errorf("Key()=%q, want %q", p.Key(), ObservationKey) } } func TestDefinition(t *testing.T) { p := &reverseZoneProvider{} def := p.Definition() if def == nil { t.Fatal("Definition() returned nil") } if def.ID != "reverse-zone" { t.Errorf("ID=%q, want reverse-zone", def.ID) } if def.Version == "" { t.Error("Version is empty") } if !def.HasHTMLReport { t.Error("HasHTMLReport should be true") } if !def.Availability.ApplyToDomain || !def.Availability.ApplyToZone { t.Errorf("Availability=%+v, want both true", def.Availability) } if len(def.ObservationKeys) != 1 || def.ObservationKeys[0] != ObservationKey { t.Errorf("ObservationKeys=%v", def.ObservationKeys) } if len(def.Rules) == 0 { t.Error("Rules() should not be empty") } if def.Interval == nil || def.Interval.Default == 0 { t.Errorf("Interval=%+v", def.Interval) } // Ensure each user option is documented with non-empty fields. for _, opt := range def.Options.UserOpts { if opt.Id == "" || opt.Type == "" || opt.Label == "" { t.Errorf("incomplete user option: %+v", opt) } } // Ensure domain options include both autofills. gotKeys := map[string]bool{} for _, opt := range def.Options.DomainOpts { gotKeys[opt.Id] = true } for _, want := range []string{"domain_name", "zone"} { if !gotKeys[want] { t.Errorf("missing domain option %q", want) } } } func TestVersionPropagation(t *testing.T) { old := Version defer func() { Version = old }() Version = "v9.9.9-test" p := &reverseZoneProvider{} def := p.Definition() if def.Version != "v9.9.9-test" { t.Errorf("def.Version=%q, want v9.9.9-test", def.Version) } } // TestObservationRoundTrip ensures the published JSON shape stays stable for // downstream consumers (the report renderer, related-observation consumers). func TestObservationRoundTrip(t *testing.T) { in := ReverseZoneData{ Zone: "1.168.192.in-addr.arpa.", IsReverseZone: true, PTRCount: 1, Entries: []PTREntry{{ OwnerName: "42.1.168.192.in-addr.arpa.", ReverseIP: "192.168.1.42", Targets: []string{"a.example."}, TargetSyntaxValid: true, ForwardAddresses: []ForwardAddress{{Type: "A", Address: "192.168.1.42", TTL: 300}}, ForwardMatch: true, TargetResolves: true, }}, } raw, err := json.Marshal(in) if err != nil { t.Fatalf("marshal: %v", err) } var out ReverseZoneData if err := json.Unmarshal(raw, &out); err != nil { t.Fatalf("unmarshal: %v", err) } if len(out.Entries) != 1 || out.Entries[0].ReverseIP != "192.168.1.42" { t.Errorf("round-trip lost data: %+v", out) } // Spot-check the JSON shape: snake_case field names that consumers rely on. var raw2 map[string]any if err := json.Unmarshal(raw, &raw2); err != nil { t.Fatalf("unmarshal map: %v", err) } for _, key := range []string{"zone", "is_reverse_zone", "ptr_count", "entries"} { if _, ok := raw2[key]; !ok { t.Errorf("missing JSON key %q in %s", key, raw) } } } // TestStaticReportContext_Empty exercises the report renderer with no data: // it should not crash and should produce some output. func TestReport_EmptyData(t *testing.T) { p := &reverseZoneProvider{} html, err := p.GetHTMLReport(sdk.StaticReportContext(nil)) if err != nil { t.Fatalf("GetHTMLReport: %v", err) } if html == "" { t.Error("expected some HTML output even for empty data") } } func TestReport_LoadError(t *testing.T) { p := &reverseZoneProvider{} raw, _ := json.Marshal(ReverseZoneData{LoadError: "no zone autofill"}) html, err := p.GetHTMLReport(sdk.StaticReportContext(raw)) if err != nil { t.Fatalf("GetHTMLReport: %v", err) } if !contains([]string{html}, html) || !containsString(html, "no zone autofill") { t.Errorf("expected LoadError message in output:\n%s", html) } if !containsString(html, "Could not load zone data") { t.Errorf("expected load-error banner in output:\n%s", html) } } func TestReport_InvalidJSON(t *testing.T) { p := &reverseZoneProvider{} _, err := p.GetHTMLReport(sdk.StaticReportContext([]byte("{not valid"))) if err == nil { t.Error("expected error for invalid JSON") } } func TestStatusToSeverity(t *testing.T) { cases := []struct { s sdk.Status want string }{ {sdk.StatusCrit, "crit"}, {sdk.StatusError, "crit"}, {sdk.StatusWarn, "warn"}, {sdk.StatusInfo, "info"}, {sdk.StatusOK, ""}, {sdk.StatusUnknown, ""}, } for _, c := range cases { if got := statusToSeverity(c.s); got != c.want { t.Errorf("statusToSeverity(%v)=%q, want %q", c.s, got, c.want) } } } func TestSeverityWeight(t *testing.T) { if severityWeight("crit") <= severityWeight("warn") { t.Error("crit should outweigh warn") } if severityWeight("warn") <= severityWeight("info") { t.Error("warn should outweigh info") } if severityWeight("info") <= severityWeight("") { t.Error("info should outweigh empty") } } func TestHintFromMeta(t *testing.T) { if hintFromMeta(nil) != "" { t.Error("nil meta should yield empty hint") } if got := hintFromMeta(map[string]any{"hint": "do this"}); got != "do this" { t.Errorf("hint key: %q", got) } if got := hintFromMeta(map[string]any{"hint": 42}); got != "" { t.Errorf("non-string hint should be ignored, got %q", got) } if got := hintFromMeta(map[string]any{"unrelated": "x"}); got != "" { t.Errorf("missing hint key should yield empty, got %q", got) } } func containsString(haystack, needle string) bool { for i := 0; i+len(needle) <= len(haystack); i++ { if haystack[i:i+len(needle)] == needle { return true } } return false }