Files
sultan/pkg/checkmk/checkmk_test.go
T
br ca6cdcca63 software steht so weit.
jetzt build + docker build und testen
2026-10-02 13:25:34 +02:00

154 lines
6.7 KiB
Go

package checkmk
import (
"strings"
"testing"
"time"
)
var now = time.Date(2026, 10, 2, 12, 0, 0, 0, time.UTC)
func lines(out string) map[string]string {
m := map[string]string{}
for _, l := range strings.Split(strings.TrimSpace(out), "\n") {
start := strings.Index(l, `"`)
end := strings.Index(l[start+1:], `"`)
m[l[start+1:start+1+end]] = l
}
return m
}
func TestTenants(t *testing.T) {
out := Render(State{Tenants: []Tenant{
{ID: "kunde_schmidt", Commercial: true, Status: "active", ValidUntil: now.AddDate(0, 0, 74)},
{ID: "meier_gmbh", Commercial: true, Status: "active", ValidUntil: now.AddDate(0, 0, 25)},
{ID: "bald", Commercial: true, Status: "active", ValidUntil: now.AddDate(0, 0, 12)},
{ID: "alt_ag", Commercial: true, Status: "expired", ValidUntil: now.AddDate(0, 0, -1)},
{ID: "homelab", Commercial: false, Status: "active", ValidUntil: now.AddDate(0, 0, -100)},
}}, now)
l := lines(out)
want := map[string]string{
"Sultan_Tenant_kunde_schmidt": `0 "Sultan_Tenant_kunde_schmidt" days_left=74;30;14 OK - Abo aktiv bis 2026-12-15 (74 Tage verbleibend)`,
"Sultan_Tenant_meier_gmbh": `1 "Sultan_Tenant_meier_gmbh" days_left=25;30;14 WARN - Abo laeuft in 25 Tagen ab! Verlaengerung erforderlich.`,
"Sultan_Tenant_bald": `2 "Sultan_Tenant_bald" days_left=12;30;14 CRIT - Abo laeuft in 12 Tagen ab! Verlaengerung erforderlich.`,
"Sultan_Tenant_alt_ag": `2 "Sultan_Tenant_alt_ag" days_left=0;30;14 CRIT - Abo seit 1 Tagen abgelaufen! Status: expired`,
}
for svc, w := range want {
if l[svc] != w {
t.Errorf("%s:\n got: %s\nwant: %s", svc, l[svc], w)
}
}
if _, ok := l["Sultan_Tenant_homelab"]; ok {
t.Error("Community-Mandant mit Vertragsfrist (5c: keine)")
}
}
func TestPayment(t *testing.T) {
out := Render(State{Tenants: []Tenant{
{ID: "a", Commercial: true, Status: "active", ValidUntil: now.AddDate(1, 0, 0), DunningLevel: 0},
{ID: "b", Commercial: true, Status: "active", ValidUntil: now.AddDate(1, 0, 0), DunningLevel: 1, OverdueSince: now.AddDate(0, 0, -9)},
{ID: "c", Commercial: true, Status: "active", ValidUntil: now.AddDate(1, 0, 0), DunningLevel: 2, OverdueSince: now.AddDate(0, 0, -26)},
{ID: "d", Commercial: true, Status: "suspended_payment", ValidUntil: now.AddDate(1, 0, 0), DunningLevel: 3, OverdueSince: now.AddDate(0, 0, -40)},
{ID: "h", Commercial: false, Status: "active", ValidUntil: now},
}}, now)
l := lines(out)
for svc, prefix := range map[string]string{
"Sultan_Payment_a": `0 "Sultan_Payment_a" dunning_level=0;1;2 OK - keine offene Zahlung`,
"Sultan_Payment_b": `1 "Sultan_Payment_b" dunning_level=1;1;2 WARN - Mahnstufe 1 (Zahlung seit 9 Tagen offen)`,
"Sultan_Payment_c": `2 "Sultan_Payment_c" dunning_level=2;1;2 CRIT - Mahnstufe 2 (Zahlung seit 26 Tagen offen)`,
"Sultan_Payment_d": `2 "Sultan_Payment_d" dunning_level=3;1;2 CRIT - Mahnstufe 3 (Zahlung seit 40 Tagen offen) - Kunde offline`,
} {
if l[svc] != prefix {
t.Errorf("%s:\n got: %s\nwant: %s", svc, l[svc], prefix)
}
}
if _, ok := l["Sultan_Payment_h"]; ok {
t.Error("Community-Mandant mit Mahnwesen")
}
}
func TestNodes(t *testing.T) {
out := Render(State{Nodes: []Node{
{ID: "online", Status: "online", LeaseUntil: now.Add(48 * time.Hour)},
{ID: "wartung", Status: "online", MaintenanceActive: true, MaintenanceUntil: now.Add(90 * time.Minute), MaintenanceReason: "Kernel 6.12", LeaseUntil: now.Add(time.Hour)},
{ID: "kurz-weg", Status: "offline", OfflineSince: now.Add(-5 * time.Minute), LeaseUntil: now.Add(time.Hour)},
{ID: "lange-weg", Status: "offline", OfflineSince: now.Add(-20 * time.Minute)},
{ID: "abgezogen", Status: "drained"},
}}, now)
l := lines(out)
want := map[string]string{
"Sultan_Node_online": `0 "Sultan_Node_online" lease_hours_left=48 OK - online`,
"Sultan_Node_wartung": `1 "Sultan_Node_wartung" lease_hours_left=1 WARN - Wartung bis 2026-10-02 13:30 UTC (Kernel 6.12)`,
"Sultan_Node_kurz-weg": `1 "Sultan_Node_kurz-weg" lease_hours_left=1 WARN - offline seit 5 min`,
"Sultan_Node_lange-weg": `2 "Sultan_Node_lange-weg" lease_hours_left=0 CRIT - offline seit 20 min`,
"Sultan_Node_abgezogen": `0 "Sultan_Node_abgezogen" lease_hours_left=0 OK - drained`,
}
for svc, w := range want {
if l[svc] != w {
t.Errorf("%s:\n got: %s\nwant: %s", svc, l[svc], w)
}
}
}
// Angemeldete Wartung: auch offline nur WARN (geplant), nicht CRIT.
func TestNodeOfflineDuringMaintenance(t *testing.T) {
out := Render(State{Nodes: []Node{{ID: "pi", Status: "offline", OfflineSince: now.Add(-time.Hour), MaintenanceActive: true}}}, now)
if !strings.HasPrefix(strings.TrimSpace(out), `1 "Sultan_Node_pi"`) || !strings.Contains(out, "Wartung unbefristet") {
t.Errorf("out = %s", out)
}
}
func TestViolations(t *testing.T) {
for n, want := range map[int]string{
0: `0 "Sultan_Policy_Violations" violations_24h=0;1 OK - keine Policy-Verstoesse in 24 h`,
3: `1 "Sultan_Policy_Violations" violations_24h=3;1 WARN - 3 Policy-Verstoesse in 24 h (siehe Server-Log)`,
} {
if got := strings.TrimSpace(Render(State{Violations24h: n}, now)); got != want {
t.Errorf("%d:\n got: %s\nwant: %s", n, got, want)
}
}
}
func TestOutputIsSortedAndASCII(t *testing.T) {
out := Render(State{
Tenants: []Tenant{{ID: "b", Commercial: true, Status: "active", ValidUntil: now.AddDate(1, 0, 0)}, {ID: "a", Commercial: true, Status: "active", ValidUntil: now.AddDate(1, 0, 0)}},
Nodes: []Node{{ID: "y", Status: "online"}, {ID: "x", Status: "online", MaintenanceActive: true, MaintenanceReason: "Grün & Ölwechsel\nzweite Zeile"}},
}, now)
if strings.Index(out, "Sultan_Tenant_a") > strings.Index(out, "Sultan_Tenant_b") || strings.Index(out, "Sultan_Node_x") > strings.Index(out, "Sultan_Node_y") {
t.Errorf("nicht sortiert:\n%s", out)
}
for _, r := range out {
if r > 127 {
t.Fatalf("Nicht-ASCII-Zeichen %q in:\n%s", r, out)
}
}
if strings.Count(out, "\n") != len(lines(out)) {
t.Errorf("Zeilenumbruch aus dem Grund in der Ausgabe:\n%s", out)
}
if !strings.Contains(out, "Gruen & Oelwechsel zweite Zeile") {
t.Errorf("Umlaute nicht umschrieben:\n%s", out)
}
}
func TestShortOfflineInSeconds(t *testing.T) {
out := Render(State{Nodes: []Node{{ID: "pi", Status: "offline", OfflineSince: now.Add(-45 * time.Second)}}}, now)
if !strings.Contains(out, "offline seit 45 s") {
t.Errorf("out = %s", out)
}
}
// Wartungsende in der Zeitzone des Servers (wie auf der Lander-Seite).
func TestMaintenanceUntilInLocation(t *testing.T) {
berlin, err := time.LoadLocation("Europe/Berlin")
if err != nil {
t.Skip("Zeitzonendaten fehlen")
}
out := Render(State{
Location: berlin,
Nodes: []Node{{ID: "pi", Status: "online", MaintenanceActive: true, MaintenanceUntil: now.Add(90 * time.Minute)}},
}, now)
if !strings.Contains(out, "Wartung bis 2026-10-02 15:30 CEST") {
t.Errorf("out = %s", out)
}
}