Files
HeatGuard/internal/report/generator_test.go
vikingowl 1c9db02334 feat: add web UI with full CRUD setup page
Add server-side rendered setup UI accessible via `heatwave web`.
The dashboard is now re-rendered per request and includes a nav bar
linking to the new /setup page. Setup provides full CRUD for profiles,
rooms, devices, occupants, AC units (with room assignment), scenario
toggles, and forecast fetching — all via POST/redirect/GET forms.

- Add ShowNav field to DashboardData for conditional nav bar
- Extract fetchForecastForProfile() for reuse by web handler
- Create setup.html.tmpl with Tailwind-styled entity sections
- Create web_handlers.go with 15 route handlers and flash cookies
- Switch web.go from pre-rendered to per-request dashboard rendering
- Graceful dashboard fallback when no forecast data exists
2026-02-09 10:39:00 +01:00

177 lines
4.0 KiB
Go

package report
import (
"os"
"strings"
"testing"
"time"
)
func testDashboardData() DashboardData {
return DashboardData{
GeneratedAt: time.Date(2025, 7, 15, 10, 0, 0, 0, time.UTC),
ProfileName: "home",
Date: "2025-07-15",
RiskLevel: "high",
PeakTempC: 37.2,
MinNightTempC: 22.5,
PoorNightCool: true,
Warnings: []WarningData{
{
Headline: "Heat warning Berlin",
Severity: "Severe",
Description: "Temperatures up to 37C",
Instruction: "Stay hydrated",
Onset: "2025-07-15 11:00",
Expires: "2025-07-16 19:00",
},
},
RiskWindows: []RiskWindowData{
{StartHour: 11, EndHour: 18, PeakTempC: 37.2, Level: "high", Reason: "very hot"},
},
Timeline: []TimelineSlotData{
{
Hour: 12, HourStr: "12:00", TempC: 35.5, RiskLevel: "high", BudgetStatus: "marginal",
Actions: []ActionData{
{Name: "Hydration reminder", Category: "hydration", Impact: "medium"},
},
},
{
Hour: 0, HourStr: "00:00", TempC: 22, RiskLevel: "low", BudgetStatus: "comfortable",
},
},
RoomBudgets: []RoomBudgetData{
{
RoomName: "Office",
InternalGainsW: 300,
SolarGainW: 600,
VentGainW: 150,
TotalGainW: 1050,
TotalGainBTUH: 3583,
ACCapacityBTUH: 8000,
HeadroomBTUH: 4417,
Status: "comfortable",
},
},
CareChecklist: []string{"Check elderly at 14:00"},
}
}
func TestGenerate_ProducesValidHTML(t *testing.T) {
html, err := GenerateString(testDashboardData())
if err != nil {
t.Fatalf("GenerateString: %v", err)
}
if !strings.Contains(html, "<!DOCTYPE html>") {
t.Error("missing DOCTYPE")
}
if !strings.Contains(html, "</html>") {
t.Error("missing closing html tag")
}
}
func TestGenerate_ContainsProfileAndDate(t *testing.T) {
html, err := GenerateString(testDashboardData())
if err != nil {
t.Fatal(err)
}
if !strings.Contains(html, "home") {
t.Error("missing profile name")
}
if !strings.Contains(html, "2025-07-15") {
t.Error("missing date")
}
}
func TestGenerate_ContainsWarning(t *testing.T) {
html, err := GenerateString(testDashboardData())
if err != nil {
t.Fatal(err)
}
if !strings.Contains(html, "Heat warning Berlin") {
t.Error("missing warning headline")
}
if !strings.Contains(html, "Stay hydrated") {
t.Error("missing warning instruction")
}
}
func TestGenerate_ContainsTimeline(t *testing.T) {
html, err := GenerateString(testDashboardData())
if err != nil {
t.Fatal(err)
}
if !strings.Contains(html, "12:00") {
t.Error("missing timeline hour")
}
if !strings.Contains(html, "Hydration reminder") {
t.Error("missing timeline action")
}
}
func TestGenerate_ContainsRoomBudget(t *testing.T) {
html, err := GenerateString(testDashboardData())
if err != nil {
t.Fatal(err)
}
if !strings.Contains(html, "Office") {
t.Error("missing room name")
}
if !strings.Contains(html, "8000 BTU/h") {
t.Error("missing AC capacity")
}
}
func TestGenerate_ContainsCareChecklist(t *testing.T) {
html, err := GenerateString(testDashboardData())
if err != nil {
t.Fatal(err)
}
if !strings.Contains(html, "Check elderly at 14:00") {
t.Error("missing care checklist item")
}
}
func TestGenerate_ContainsCSS(t *testing.T) {
html, err := GenerateString(testDashboardData())
if err != nil {
t.Fatal(err)
}
if !strings.Contains(html, "box-sizing") {
t.Error("missing inlined CSS")
}
}
func TestGenerate_EmptyData(t *testing.T) {
_, err := GenerateString(DashboardData{
GeneratedAt: time.Now(),
ProfileName: "test",
Date: "2025-07-15",
})
if err != nil {
t.Fatalf("should handle empty data: %v", err)
}
}
func TestGenerate_WriteFile(t *testing.T) {
path := t.TempDir() + "/report.html"
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
if err := Generate(f, testDashboardData()); err != nil {
t.Fatalf("Generate to file: %v", err)
}
f.Close()
info, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
if info.Size() < 1000 {
t.Errorf("report file too small: %d bytes", info.Size())
}
}