test / test (push) Canceled after 0s
M14, M15: queryEach gives each resolver 2 s within the check's context and asks the one that answered last first; an address or PTR lookup not answered is Unknown, an answered mismatch beside it still an error; Checker.Server keeps the last answer's addresses, so SPF does not go Unknown with it. Notifications report faults only, now tested with SPF unknown alone and beside a wrong DKIM. DMARC reports: tests of malformed XML, gzip and zip bombs, a message over the limit, the service's Resync, hosted suggestion and validation. The finding: both limits cut silently and an oversized report failed as damaged; readAtMost refuses it for its size (MaxMessage 10 MiB, moved into IngestMessage). healthReadings replaces map[string]any for Health and Overview. L5: an inbound upstream has one TLS mode - a second domain to the same [host]:port with another mode is refused, naming the first; the TLS policy map holds each next hop once. Tested on the test server: go vet + go test of 25 packages, make e2e (all pass, 631 s) on this tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
5.4 KiB
Go
130 lines
5.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/mixeme/selfpost/internal/dnscheck"
|
|
"github.com/mixeme/selfpost/internal/health"
|
|
)
|
|
|
|
// checksFixture is one reading of every server check, shaped as healthChecks
|
|
// returns it.
|
|
func checksFixture() healthReadings {
|
|
return healthReadings{
|
|
Processes: []health.Process{
|
|
{Name: "opendkim", State: "RUNNING", Status: health.StatusOK},
|
|
{Name: "postfix", State: "FATAL", Status: health.StatusError},
|
|
},
|
|
ProcessError: false,
|
|
ProcessStatus: health.StatusError,
|
|
QueueSummary: "Mail queue is empty",
|
|
QueueError: "",
|
|
QueueStatus: health.StatusOK,
|
|
Machine: health.Machine{Status: health.StatusUnknown},
|
|
Cert: health.Certificate{Subject: "mail.example.org", DaysLeft: 12, Status: health.StatusWarn, Detail: "Expires soon."},
|
|
Sockets: []health.Socket{
|
|
{Name: "OpenDKIM", Present: true, Status: health.StatusOK},
|
|
{Name: "send-log", Present: false, Status: health.StatusWarn},
|
|
},
|
|
SocketStatus: health.StatusWarn,
|
|
Hostname: "mail.example.org",
|
|
PTR: dnscheck.Result{Status: health.StatusOK},
|
|
}
|
|
}
|
|
|
|
// Overview is one verdict per check: six cards in the order of the tables on
|
|
// Health, each saying in words what its table would, and each leading there.
|
|
func TestHealthCards(t *testing.T) {
|
|
cards := healthCards(checksFixture())
|
|
want := []struct{ name, level, value, sub, href string }{
|
|
{"Machine", "warn", "No reading", "", "/server/health"},
|
|
{"Processes", "fail", "1 of 2 running", "postfix", "/server/health"},
|
|
{"TLS certificate", "warn", "Expires in 12 days", "mail.example.org", "/server/health"},
|
|
{"Queue", "", "Empty", "outbound", "/outbound/queue"},
|
|
{"Milter sockets", "warn", "1 of 2 answering", "send-log", "/server/health"},
|
|
{"Reverse DNS", "", "Forward = reverse", "mail.example.org", "/server/health"},
|
|
}
|
|
if len(cards) != len(want) {
|
|
t.Fatalf("%d cards, want %d", len(cards), len(want))
|
|
}
|
|
for i, w := range want {
|
|
c := cards[i]
|
|
if c.Name != w.name || c.Level != w.level || c.Value != w.value || c.Sub != w.sub || c.Href != w.href || c.Icon == "" {
|
|
t.Errorf("card %d = %+v, want %+v", i, c, w)
|
|
}
|
|
}
|
|
|
|
// A check that could not run must not look fine.
|
|
c := checksFixture()
|
|
c.ProcessError = true
|
|
c.ProcessStatus = health.StatusUnknown
|
|
c.PTR = dnscheck.Result{Status: health.StatusUnknown}
|
|
cards = healthCards(c)
|
|
if cards[1].Level != "warn" || cards[1].Value != "Could not be read" || cards[5].Level != "warn" || cards[5].Value != "Not checked" {
|
|
t.Errorf("unverified checks render as %+v and %+v", cards[1], cards[5])
|
|
}
|
|
}
|
|
|
|
// The Queue card has room for a few words: postqueue's own last line is cut
|
|
// down to what it says.
|
|
func TestQueueCardValue(t *testing.T) {
|
|
for in, want := range map[string]string{
|
|
"": "Empty",
|
|
"Mail queue is empty": "Empty",
|
|
"3 Kbytes in 2 Requests.": "2 queued",
|
|
"1 Kbytes in 1 Request.": "1 queued",
|
|
"something postfix invented": "something postfix invented",
|
|
} {
|
|
if got := queueCardValue(in); got != want {
|
|
t.Errorf("queueCardValue(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The Processes card counts the programs that are meant to stay up. The one-shot
|
|
// reload helper idles between runs: it is neither running nor missing.
|
|
func TestProcessesCardLeavesOutTheIdleOneShot(t *testing.T) {
|
|
c := checksFixture()
|
|
c.Processes = []health.Process{
|
|
{Name: "opendkim", State: "RUNNING", Status: health.StatusOK},
|
|
{Name: "postfix", State: "RUNNING", Status: health.StatusOK},
|
|
{Name: "postfix-reload", State: "STOPPED", Status: health.StatusOK, OneShot: true},
|
|
}
|
|
c.ProcessStatus = health.StatusOK
|
|
card := healthCards(c)[1]
|
|
if card.Value != "2 of 2 running" || card.Sub != "all programs" {
|
|
t.Errorf("card = %q / %q, want 2 of 2 running / all programs", card.Value, card.Sub)
|
|
}
|
|
|
|
c.Processes = []health.Process{
|
|
{Name: "opendkim", State: "RUNNING", Status: health.StatusOK},
|
|
{Name: "postfix-reload", State: "FATAL", Status: health.StatusError, OneShot: true},
|
|
}
|
|
card = healthCards(c)[1]
|
|
if card.Value != "1 of 1 running" || card.Sub != "postfix-reload" {
|
|
t.Errorf("card = %q / %q, want 1 of 1 running / postfix-reload", card.Value, card.Sub)
|
|
}
|
|
}
|
|
|
|
// An idle one-shot helper is "on demand", not "stopped"; a daemon keeps its
|
|
// state word, and a one-shot in trouble keeps its own.
|
|
func TestProcessRowOfAnIdleOneShotSaysOnDemand(t *testing.T) {
|
|
idle := processRow(health.Process{Name: "postfix-reload", State: "STOPPED", Detail: "Not started", Status: health.StatusOK, OneShot: true})
|
|
if idle.State.Label != "on demand" || idle.State.Status != "off" || !strings.Contains(idle.Detail, "not run since the container started") {
|
|
t.Errorf("idle one-shot = %+v", idle)
|
|
}
|
|
ran := processRow(health.Process{Name: "postfix-reload", State: "EXITED", Detail: "Oct 10 08:42 PM", Status: health.StatusOK, OneShot: true})
|
|
if ran.State.Label != "on demand" || !strings.HasSuffix(ran.Detail, "last run: Oct 10 08:42 PM") {
|
|
t.Errorf("one-shot that ran = %+v", ran)
|
|
}
|
|
bad := processRow(health.Process{Name: "postfix-reload", State: "FATAL", Detail: "Exited too quickly", Status: health.StatusError, OneShot: true})
|
|
if bad.State.Label != "fatal" || bad.State.Status != "error" || bad.Detail != "Exited too quickly" {
|
|
t.Errorf("one-shot in trouble = %+v", bad)
|
|
}
|
|
daemon := processRow(health.Process{Name: "postfix", State: "STOPPED", Detail: "Oct 10 08:42 PM", Status: health.StatusError})
|
|
if daemon.State.Label != "stopped" || daemon.State.Status != "error" {
|
|
t.Errorf("stopped daemon = %+v", daemon)
|
|
}
|
|
}
|