package checker import ( "encoding/json" "testing" "time" sdk "git.happydns.org/checker-sdk-go/checker" ) func mustJSON(t *testing.T, v any) json.RawMessage { t.Helper() b, err := json.Marshal(v) if err != nil { t.Fatalf("marshal: %v", err) } return b } func TestTLSProbeView_AddressEndpointWins(t *testing.T) { v := tlsProbeView{Endpoint: "mx.example.com:25", Host: "ignored", Port: 999} if got := v.address(); got != "mx.example.com:25" { t.Errorf("got %q", got) } } func TestTLSProbeView_AddressFromHostPort(t *testing.T) { v := tlsProbeView{Host: "mx.example.com", Port: 25} if got := v.address(); got != "mx.example.com:25" { t.Errorf("got %q", got) } } func TestTLSProbeView_AddressEmpty(t *testing.T) { v := tlsProbeView{} if got := v.address(); got != "" { t.Errorf("expected empty, got %q", got) } v2 := tlsProbeView{Host: "only-host"} if got := v2.address(); got != "" { t.Errorf("host without port should be empty, got %q", got) } } func TestParseTLSRelated_KeyedByRef(t *testing.T) { payload := map[string]any{ "probes": map[string]any{ "ref-A": map[string]any{"host": "mx.example.com", "port": 25, "tls_version": "TLS1.3"}, }, } r := sdk.RelatedObservation{Ref: "ref-A", Data: mustJSON(t, payload)} v := parseTLSRelated(r) if v == nil { t.Fatal("expected match") } if v.TLSVersion != "TLS1.3" { t.Errorf("got %q", v.TLSVersion) } } func TestParseTLSRelated_KeyedRefMissing(t *testing.T) { payload := map[string]any{ "probes": map[string]any{ "some-other-ref": map[string]any{"host": "mx", "port": 25}, }, } r := sdk.RelatedObservation{Ref: "ref-A", Data: mustJSON(t, payload)} if got := parseTLSRelated(r); got != nil { t.Errorf("expected nil for missing ref, got %+v", got) } } func TestParseTLSRelated_FlatTopLevel(t *testing.T) { payload := map[string]any{"host": "mx.example.com", "port": 25} r := sdk.RelatedObservation{Data: mustJSON(t, payload)} v := parseTLSRelated(r) if v == nil || v.Host != "mx.example.com" { t.Errorf("got %+v", v) } } func TestParseTLSRelated_BadJSON(t *testing.T) { r := sdk.RelatedObservation{Data: json.RawMessage("not json at all")} if got := parseTLSRelated(r); got != nil { t.Errorf("expected nil for bad json, got %+v", got) } } func TestTLSIssuesFromRelated_FromIssuesList(t *testing.T) { payload := map[string]any{ "host": "mx.example.com", "port": 25, "issues": []map[string]any{ {"code": "cert.expired", "severity": "crit", "message": "expired", "fix": "renew"}, {"code": "cert.weakcipher", "severity": "WARN", "message": "weak"}, {"code": "ignore-me", "severity": "bogus"}, // unknown severity → skipped }, } related := []sdk.RelatedObservation{{Data: mustJSON(t, payload)}} issues := tlsIssuesFromRelated(related) if len(issues) != 2 { t.Fatalf("want 2 issues, got %d (%+v)", len(issues), issues) } if issues[0].Code != "smtp.tls.cert.expired" || issues[0].Severity != SeverityCrit { t.Errorf("first: %+v", issues[0]) } if issues[1].Severity != SeverityWarn { t.Errorf("second severity: %q", issues[1].Severity) } } func TestTLSIssuesFromRelated_EmptyCode(t *testing.T) { payload := map[string]any{ "host": "mx", "port": 25, "issues": []map[string]any{{"severity": "warn", "message": "x"}}, } related := []sdk.RelatedObservation{{Data: mustJSON(t, payload)}} issues := tlsIssuesFromRelated(related) if len(issues) != 1 || issues[0].Code != "smtp.tls.tls.unknown" { t.Errorf("expected fallback code, got %+v", issues) } } func TestTLSIssuesFromRelated_FromShorthand_ChainInvalid(t *testing.T) { chainBad := false payload := map[string]any{ "host": "mx", "port": 25, "chain_valid": chainBad, } related := []sdk.RelatedObservation{{Data: mustJSON(t, payload)}} issues := tlsIssuesFromRelated(related) if len(issues) != 1 || issues[0].Severity != SeverityCrit { t.Errorf("expected single crit issue, got %+v", issues) } } func TestTLSIssuesFromRelated_HostnameMismatch(t *testing.T) { hn := false payload := map[string]any{"host": "mx", "port": 25, "hostname_match": hn} related := []sdk.RelatedObservation{{Data: mustJSON(t, payload)}} issues := tlsIssuesFromRelated(related) if len(issues) != 1 || issues[0].Severity != SeverityCrit { t.Errorf("expected hostname-mismatch crit, got %+v", issues) } } func TestTLSIssuesFromRelated_ExpiringSoon(t *testing.T) { soon := time.Now().Add(48 * time.Hour) payload := map[string]any{"host": "mx", "port": 25, "not_after": soon} issues := tlsIssuesFromRelated([]sdk.RelatedObservation{{Data: mustJSON(t, payload)}}) if len(issues) != 1 || issues[0].Severity != SeverityWarn { t.Errorf("expected warn, got %+v", issues) } } func TestTLSIssuesFromRelated_Expired(t *testing.T) { past := time.Now().Add(-1 * time.Hour) payload := map[string]any{"host": "mx", "port": 25, "not_after": past} issues := tlsIssuesFromRelated([]sdk.RelatedObservation{{Data: mustJSON(t, payload)}}) if len(issues) != 1 || issues[0].Severity != SeverityCrit { t.Errorf("expected crit (expired), got %+v", issues) } } func TestTLSIssuesFromRelated_NoSeverityNoIssue(t *testing.T) { yes := true notAfter := time.Now().Add(365 * 24 * time.Hour) payload := map[string]any{ "host": "mx", "port": 25, "chain_valid": yes, "hostname_match": yes, "not_after": notAfter, } if got := tlsIssuesFromRelated([]sdk.RelatedObservation{{Data: mustJSON(t, payload)}}); len(got) != 0 { t.Errorf("happy path: expected no issues, got %+v", got) } } func TestWorstSeverity_Ordering(t *testing.T) { v := tlsProbeView{ Issues: []struct { Code string `json:"code"` Severity string `json:"severity"` Message string `json:"message,omitempty"` Fix string `json:"fix,omitempty"` }{ {Code: "a", Severity: "info"}, {Code: "b", Severity: "warn"}, }, } if got := v.worstSeverity(); got != SeverityWarn { t.Errorf("info+warn → warn, got %q", got) } v.Issues = append(v.Issues, struct { Code string `json:"code"` Severity string `json:"severity"` Message string `json:"message,omitempty"` Fix string `json:"fix,omitempty"` }{Code: "c", Severity: "CRIT"}) if got := v.worstSeverity(); got != SeverityCrit { t.Errorf("with crit → crit, got %q", got) } }