240 lines
7.1 KiB
Go
240 lines
7.1 KiB
Go
package checker
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
func TestStatusToSeverity(t *testing.T) {
|
|
cases := []struct {
|
|
in sdk.Status
|
|
want string
|
|
}{
|
|
{sdk.StatusCrit, SeverityCrit},
|
|
{sdk.StatusError, SeverityCrit},
|
|
{sdk.StatusWarn, SeverityWarn},
|
|
{sdk.StatusInfo, SeverityInfo},
|
|
{sdk.StatusOK, ""},
|
|
{sdk.StatusUnknown, ""},
|
|
}
|
|
for _, c := range cases {
|
|
if got := statusToSeverity(c.in); got != c.want {
|
|
t.Errorf("status %v: want %q, got %q", c.in, c.want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOverallStatus_NullMX(t *testing.T) {
|
|
d := &SMTPData{MX: MXLookup{NullMX: true}}
|
|
label, class := overallStatus(d, nil, nil)
|
|
if label != "NULL MX" || class != "info" {
|
|
t.Errorf("got (%q,%q)", label, class)
|
|
}
|
|
}
|
|
|
|
func TestOverallStatus_DataOnly(t *testing.T) {
|
|
d := &SMTPData{}
|
|
label, class := overallStatus(d, nil, nil)
|
|
if label != "data only" || class != "muted" {
|
|
t.Errorf("got (%q,%q)", label, class)
|
|
}
|
|
}
|
|
|
|
func TestOverallStatus_FromFixes(t *testing.T) {
|
|
d := &SMTPData{}
|
|
states := []sdk.CheckState{{Status: sdk.StatusOK}}
|
|
cases := []struct {
|
|
fixes []reportFix
|
|
wantLabel string
|
|
wantClass string
|
|
caseLabel string
|
|
}{
|
|
{[]reportFix{{Severity: SeverityCrit}}, "FAIL", "fail", "crit"},
|
|
{[]reportFix{{Severity: SeverityWarn}}, "WARN", "warn", "warn"},
|
|
{[]reportFix{{Severity: SeverityInfo}}, "INFO", "info", "info"},
|
|
{nil, "OK", "ok", "ok"},
|
|
}
|
|
for _, c := range cases {
|
|
label, class := overallStatus(d, states, c.fixes)
|
|
if label != c.wantLabel || class != c.wantClass {
|
|
t.Errorf("%s: got (%q,%q)", c.caseLabel, label, class)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOverallStatus_CritWinsOverWarn(t *testing.T) {
|
|
d := &SMTPData{}
|
|
states := []sdk.CheckState{{Status: sdk.StatusOK}}
|
|
fixes := []reportFix{{Severity: SeverityWarn}, {Severity: SeverityCrit}, {Severity: SeverityInfo}}
|
|
if label, _ := overallStatus(d, states, fixes); label != "FAIL" {
|
|
t.Errorf("crit must dominate, got %q", label)
|
|
}
|
|
}
|
|
|
|
func TestFixesFromStates_OnlyFindings(t *testing.T) {
|
|
states := []sdk.CheckState{
|
|
{Status: sdk.StatusOK, Code: "skip-me"},
|
|
{Status: sdk.StatusUnknown, Code: "skip-me-too"},
|
|
{Status: sdk.StatusWarn, Code: "warn-1", Message: "msg", Meta: map[string]any{"fix": "do x", "endpoint": "1.2.3.4:25", "target": "mx"}},
|
|
{Status: sdk.StatusCrit, Code: "crit-1", Message: "boom"},
|
|
}
|
|
out := fixesFromStates(states)
|
|
if len(out) != 2 {
|
|
t.Fatalf("want 2 fixes, got %d", len(out))
|
|
}
|
|
w := out[0]
|
|
if w.Severity != SeverityWarn || w.Fix != "do x" || w.Endpoint != "1.2.3.4:25" || w.Target != "mx" {
|
|
t.Errorf("warn fix wrong: %+v", w)
|
|
}
|
|
}
|
|
|
|
func TestFixesFromStates_MetaWrongTypesIgnored(t *testing.T) {
|
|
states := []sdk.CheckState{
|
|
{Status: sdk.StatusWarn, Code: "x", Meta: map[string]any{"fix": 42, "endpoint": nil}},
|
|
}
|
|
out := fixesFromStates(states)
|
|
if len(out) != 1 {
|
|
t.Fatalf("got %d", len(out))
|
|
}
|
|
if out[0].Fix != "" || out[0].Endpoint != "" {
|
|
t.Errorf("non-string meta values must be ignored, got %+v", out[0])
|
|
}
|
|
}
|
|
|
|
func TestIndexTLSByAddress(t *testing.T) {
|
|
yes := true
|
|
notAfter := time.Now().Add(30 * 24 * time.Hour)
|
|
payload := map[string]any{
|
|
"host": "mx.example.com", "port": 25,
|
|
"chain_valid": yes, "hostname_match": yes, "not_after": notAfter,
|
|
"issues": []map[string]any{
|
|
{"code": "x", "severity": "warn", "message": "m"},
|
|
{"code": "y", "severity": "bogus"}, // dropped
|
|
},
|
|
}
|
|
related := []sdk.RelatedObservation{{Data: mustJSON(t, payload), CollectedAt: time.Now()}}
|
|
idx := indexTLSByAddress(related)
|
|
posture, ok := idx["mx.example.com:25"]
|
|
if !ok {
|
|
t.Fatalf("expected entry, got %+v", idx)
|
|
}
|
|
if posture.ChainValid == nil || !*posture.ChainValid {
|
|
t.Errorf("ChainValid: %+v", posture.ChainValid)
|
|
}
|
|
if len(posture.Issues) != 1 {
|
|
t.Errorf("issues: want 1, got %d", len(posture.Issues))
|
|
}
|
|
}
|
|
|
|
func TestBuildReportData_StatusByEndpoint(t *testing.T) {
|
|
yes := true
|
|
relay := true
|
|
d := &SMTPData{
|
|
Domain: "example.com",
|
|
MX: MXLookup{Records: []MXRecord{{Preference: 10, Target: "mx.example.com", IPv4: []string{"1.2.3.4"}}}},
|
|
Endpoints: []EndpointProbe{
|
|
// healthy
|
|
{
|
|
Target: "mx.example.com", IP: "1.2.3.4", Port: 25, Address: "1.2.3.4:25",
|
|
TCPConnected: true, BannerReceived: true, BannerCode: 220,
|
|
EHLOReceived: true, STARTTLSOffered: true, STARTTLSUpgraded: true,
|
|
NullSenderAccepted: &yes, PostmasterAccepted: &yes,
|
|
},
|
|
// unreachable
|
|
{Target: "mx.example.com", IP: "1.2.3.5", Port: 25, Address: "1.2.3.5:25"},
|
|
// open relay
|
|
{
|
|
Target: "mx.example.com", IP: "1.2.3.6", Port: 25, Address: "1.2.3.6:25",
|
|
TCPConnected: true, BannerReceived: true, BannerCode: 220, EHLOReceived: true,
|
|
STARTTLSOffered: true, STARTTLSUpgraded: true,
|
|
OpenRelay: &relay,
|
|
},
|
|
},
|
|
}
|
|
view := buildReportData(d, nil, []sdk.CheckState{{Status: sdk.StatusOK}})
|
|
if len(view.Endpoints) != 3 {
|
|
t.Fatalf("want 3 endpoints, got %d", len(view.Endpoints))
|
|
}
|
|
wantStatuses := []string{"OK", "unreachable", "OPEN RELAY"}
|
|
for i, want := range wantStatuses {
|
|
if view.Endpoints[i].StatusLabel != want {
|
|
t.Errorf("endpoint[%d]: got %q, want %q", i, view.Endpoints[i].StatusLabel, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildReportData_FixesSortedBySeverity(t *testing.T) {
|
|
d := &SMTPData{Domain: "x"}
|
|
states := []sdk.CheckState{
|
|
{Status: sdk.StatusInfo, Code: "info-1"},
|
|
{Status: sdk.StatusCrit, Code: "crit-1"},
|
|
{Status: sdk.StatusWarn, Code: "warn-1"},
|
|
}
|
|
view := buildReportData(d, nil, states)
|
|
if len(view.Fixes) != 3 {
|
|
t.Fatalf("got %d fixes", len(view.Fixes))
|
|
}
|
|
if view.Fixes[0].Severity != SeverityCrit ||
|
|
view.Fixes[1].Severity != SeverityWarn ||
|
|
view.Fixes[2].Severity != SeverityInfo {
|
|
t.Errorf("not sorted: %+v", view.Fixes)
|
|
}
|
|
}
|
|
|
|
func TestRenderReport_ContainsDomain(t *testing.T) {
|
|
view := reportData{
|
|
Domain: "example.com",
|
|
StatusLabel: "OK",
|
|
StatusClass: "ok",
|
|
}
|
|
html, err := renderReport(view)
|
|
if err != nil {
|
|
t.Fatalf("render: %v", err)
|
|
}
|
|
if !strings.Contains(html, "example.com") {
|
|
t.Errorf("html missing domain")
|
|
}
|
|
if !strings.Contains(html, "<!DOCTYPE html>") {
|
|
t.Errorf("not an html doc")
|
|
}
|
|
}
|
|
|
|
func TestGetHTMLReport_RoundTrip(t *testing.T) {
|
|
yes := true
|
|
d := &SMTPData{
|
|
Domain: "example.com",
|
|
RunAt: "2026-01-01T00:00:00Z",
|
|
MX: MXLookup{Records: []MXRecord{{Preference: 10, Target: "mx.example.com", IPv4: []string{"1.2.3.4"}}}},
|
|
Endpoints: []EndpointProbe{{
|
|
Target: "mx.example.com", IP: "1.2.3.4", Port: 25, Address: "1.2.3.4:25",
|
|
TCPConnected: true, BannerReceived: true, BannerCode: 220,
|
|
EHLOReceived: true, STARTTLSOffered: true, STARTTLSUpgraded: true,
|
|
NullSenderAccepted: &yes, PostmasterAccepted: &yes,
|
|
}},
|
|
}
|
|
body, err := json.Marshal(d)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
rctx := sdk.StaticReportContext(body)
|
|
p := &smtpProvider{}
|
|
html, err := p.GetHTMLReport(rctx)
|
|
if err != nil {
|
|
t.Fatalf("GetHTMLReport: %v", err)
|
|
}
|
|
if !strings.Contains(html, "mx.example.com") {
|
|
t.Errorf("html missing target hostname")
|
|
}
|
|
}
|
|
|
|
func TestGetHTMLReport_BadJSON(t *testing.T) {
|
|
rctx := sdk.StaticReportContext(json.RawMessage("{not json"))
|
|
p := &smtpProvider{}
|
|
if _, err := p.GetHTMLReport(rctx); err == nil {
|
|
t.Fatal("expected error on bad json")
|
|
}
|
|
}
|