Files
selfpost/internal/web/handlers/handlers_status_test.go
T
mixandClaude Opus 5.5 baa053d250 panel-redesign: three things the first look at the deployed panel showed
Seen by the owner on the test server, on real data (2026-10-10).

  - Overview's Processes card said "7 of 7 running" with six programs
    running and postfix-reload idle. The card counts the programs that
    are meant to stay up; a one-shot helper (health.Process.OneShot) is
    left out of both numbers and named only when it is in trouble.
  - The Milter sockets card clipped its second line ("OpenDKIM,
    send-log,...") once the spam filter was the third socket. The line
    names the sockets that do not answer when some do not; with all
    answering it is their names, or "incl. the spam filter" - the
    mockup's words - when the filter is among them.
  - A real 2048-bit DKIM value did not fit the three rows of its field
    and scrolled inside it. The field is as tall as the value needs
    (3 to 12 rows).

The fixtures had a shortened key, two sockets and no one-shot program,
so none of this showed in the evidence screenshots.

  go test -count=1 ./internal/health/... ./internal/web/...: ok

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:39:39 +03:00

108 lines
4.2 KiB
Go

package handlers
import (
"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() map[string]any {
return map[string]any{
"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#machine"},
{"Processes", "fail", "1 of 2 running", "postfix", "/server/health#processes"},
{"TLS certificate", "warn", "Expires in 12 days", "mail.example.org", "/server/health#certificate"},
{"Queue", "", "Empty", "outbound", "/outbound/queue"},
{"Milter sockets", "warn", "1 of 2 answering", "send-log", "/server/health#sockets"},
{"Reverse DNS", "", "Forward = reverse", "mail.example.org", "/server/health#hostname"},
}
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)
}
}