checker-reverse-zone/checker/provider_test.go

206 lines
5.7 KiB
Go

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
}