ad61e96abd
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
116 lines
4.2 KiB
Go
116 lines
4.2 KiB
Go
package web
|
||
|
||
import (
|
||
"bytes"
|
||
"io"
|
||
"log"
|
||
"net/http/httptest"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
||
)
|
||
|
||
// Catches template SYNTAX errors (template.Must in New panics) and that the per-customer floor renders
|
||
// on the Customers list. Field-level execution errors surface as a non-nil ExecuteTemplate error.
|
||
func TestTemplates_FloorRender(t *testing.T) {
|
||
st, err := store.New(filepath.Join(t.TempDir(), "t.db"), log.New(io.Discard, "", 0))
|
||
if err != nil {
|
||
t.Fatalf("store.New: %v", err)
|
||
}
|
||
t.Cleanup(func() { st.Close() })
|
||
s := New(st, "", "", "test", time.Hour, log.New(io.Discard, "", 0)) // template.Must runs here
|
||
|
||
// configs.html — the list page data shape used by handleConfigList (global floor/artifacts moved
|
||
// to the Configuration tab; the per-customer effective floor still renders here).
|
||
cfgData := struct {
|
||
Customers []customerListEntry
|
||
ActiveNav string
|
||
Flash string
|
||
}{
|
||
Customers: []customerListEntry{{
|
||
CustomerID: "c", EffectiveFloor: "0.87.0", FloorOverride: "0.87.0", BelowFloor: true,
|
||
ControllerVersion: "0.86.0", HasConfig: true,
|
||
}},
|
||
ActiveNav: "configs",
|
||
}
|
||
var buf bytes.Buffer
|
||
if err := s.templates.ExecuteTemplate(&buf, "configs.html", cfgData); err != nil {
|
||
t.Fatalf("render configs.html: %v", err)
|
||
}
|
||
if !bytes.Contains(buf.Bytes(), []byte("0.87.0")) {
|
||
t.Errorf("configs.html missing per-customer floor content")
|
||
}
|
||
// The global floor + artifact cards must NOT be on the Customers page anymore.
|
||
if bytes.Contains(buf.Bytes(), []byte("global floor")) || bytes.Contains(buf.Bytes(), []byte("Day-0 artifacts")) {
|
||
t.Errorf("configs.html still renders global settings that moved to Configuration")
|
||
}
|
||
|
||
// configuration.html — the Configuration tab now carries the global floor + artifact settings.
|
||
confData := map[string]interface{}{
|
||
"CSRFToken": "tok",
|
||
"CSRFField": s.csrfField(httptest.NewRequest("GET", "/", nil)),
|
||
"AssetCount": 0,
|
||
"AssetLastSync": "",
|
||
"GlobalFloor": "0.86.0",
|
||
"Artifacts": store.ArtifactManifest{AgentVersion: "0.43.0", GoldenVersion: "0.85.1"},
|
||
"Flash": "artifacts_set",
|
||
}
|
||
buf.Reset()
|
||
if err := s.templates.ExecuteTemplate(&buf, "configuration.html", confData); err != nil {
|
||
t.Fatalf("render configuration.html: %v", err)
|
||
}
|
||
body := buf.String()
|
||
for _, want := range []string{"global floor", "Day-0 artifacts", "0.86.0", "0.43.0",
|
||
"/configuration/global-floor", "/configuration/artifacts", "Artifact manifest saved"} {
|
||
if !strings.Contains(body, want) {
|
||
t.Errorf("configuration.html missing %q", want)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Scenario D of the v0.31.0 critical-severity fix: a customer with critical events must render a
|
||
// distinct severity-critical count badge on the dashboard, ordered BEFORE the error badge.
|
||
// (Pre-fix consumer gap: criticals were accepted but invisible in the 24h summary counts.)
|
||
func TestTemplates_DashboardCriticalBadge(t *testing.T) {
|
||
st, err := store.New(filepath.Join(t.TempDir(), "t.db"), log.New(io.Discard, "", 0))
|
||
if err != nil {
|
||
t.Fatalf("store.New: %v", err)
|
||
}
|
||
t.Cleanup(func() { st.Close() })
|
||
s := New(st, "", "", "test", time.Hour, log.New(io.Discard, "", 0))
|
||
|
||
// Mirrors the anonymous dashboardCustomer struct in handleDashboard.
|
||
type dashboardCustomer struct {
|
||
store.CustomerSummary
|
||
OverallStatus string
|
||
BackupAge string
|
||
EventCriticals int
|
||
EventErrors int
|
||
EventWarnings int
|
||
}
|
||
data := []dashboardCustomer{{
|
||
CustomerSummary: store.CustomerSummary{CustomerID: "c1", CustomerName: "Acme", ReceivedAt: time.Now()},
|
||
OverallStatus: "ok", BackupAge: "–",
|
||
EventCriticals: 2, EventErrors: 1, EventWarnings: 0,
|
||
}}
|
||
var buf bytes.Buffer
|
||
if err := s.templates.ExecuteTemplate(&buf, "dashboard.html", data); err != nil {
|
||
t.Fatalf("render dashboard.html: %v", err)
|
||
}
|
||
body := buf.String()
|
||
critIdx := strings.Index(body, `severity-badge severity-critical">2<`)
|
||
errIdx := strings.Index(body, `severity-badge severity-error">1<`)
|
||
if critIdx < 0 {
|
||
t.Fatalf("dashboard.html missing the severity-critical count badge")
|
||
}
|
||
if errIdx < 0 {
|
||
t.Fatalf("dashboard.html missing the severity-error count badge")
|
||
}
|
||
if critIdx > errIdx {
|
||
t.Errorf("critical badge renders AFTER the error badge (crit@%d, err@%d) — must be first", critIdx, errIdx)
|
||
}
|
||
}
|