Files
dnswatcher/internal/notify/history_test.go
user 713a758c83
All checks were successful
check / check (push) Successful in 4s
feat: add unauthenticated web dashboard showing monitoring state and recent alerts
Add a read-only web dashboard at GET / that displays:
- Summary counts for all monitored resources
- Domain nameserver state with per-NS records and status
- Hostname DNS records per authoritative nameserver
- TCP port open/closed state with associated hostnames
- TLS certificate details (CN, issuer, expiry, status)
- Last 100 alerts in reverse chronological order

Every data point shows relative age (e.g. '5m ago') for freshness.
Page auto-refreshes every 30 seconds via meta refresh.

Uses Tailwind CSS via CDN for a dark, technical aesthetic with
saturated teals and blues on dark slate. Single page, no navigation.

Implementation:
- internal/notify/history.go: thread-safe ring buffer (last 100 alerts)
- internal/notify/notify.go: record alerts in history before dispatch,
  refactor SendNotification into smaller dispatch helpers (funlen)
- internal/handlers/dashboard.go: template rendering with embedded HTML,
  helper functions for relative time, record formatting, expiry days
- internal/handlers/templates/dashboard.html: Tailwind-styled dashboard
- internal/handlers/handlers.go: add State and Notify dependencies
- internal/server/routes.go: register GET / dashboard route
- README.md: document dashboard and new / endpoint

No secrets (webhook URLs, API tokens, notification endpoints) are
exposed in the dashboard.

closes #82
2026-03-04 03:06:07 -08:00

89 lines
1.5 KiB
Go

package notify_test
import (
"testing"
"time"
"sneak.berlin/go/dnswatcher/internal/notify"
)
func TestAlertHistoryEmpty(t *testing.T) {
t.Parallel()
h := notify.NewAlertHistory()
entries := h.Recent()
if len(entries) != 0 {
t.Fatalf("expected 0 entries, got %d", len(entries))
}
}
func TestAlertHistoryAddAndRecent(t *testing.T) {
t.Parallel()
h := notify.NewAlertHistory()
now := time.Now().UTC()
h.Add(notify.AlertEntry{
Timestamp: now.Add(-2 * time.Minute),
Title: "first",
Message: "msg1",
Priority: "info",
})
h.Add(notify.AlertEntry{
Timestamp: now.Add(-1 * time.Minute),
Title: "second",
Message: "msg2",
Priority: "warning",
})
entries := h.Recent()
if len(entries) != 2 {
t.Fatalf("expected 2 entries, got %d", len(entries))
}
// Newest first.
if entries[0].Title != "second" {
t.Errorf(
"expected newest first, got %q", entries[0].Title,
)
}
if entries[1].Title != "first" {
t.Errorf(
"expected oldest second, got %q", entries[1].Title,
)
}
}
func TestAlertHistoryOverflow(t *testing.T) {
t.Parallel()
h := notify.NewAlertHistory()
const totalEntries = 110
// Fill beyond capacity.
for i := range totalEntries {
h.Add(notify.AlertEntry{
Timestamp: time.Now().UTC(),
Title: "alert",
Message: "msg",
Priority: string(rune('0' + i%10)),
})
}
entries := h.Recent()
const maxHistory = 100
if len(entries) != maxHistory {
t.Fatalf(
"expected %d entries, got %d",
maxHistory, len(entries),
)
}
}