checker-smtp/checker/tls_related_test.go

200 lines
6.1 KiB
Go

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)
}
}