Files
HeatGuard/internal/heat/internal_gains_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

97 lines
2.4 KiB
Go

package heat
import (
"testing"
)
func TestDeviceHeatGain(t *testing.T) {
tests := []struct {
name string
dev Device
mode DeviceMode
want float64
}{
{
name: "idle mode uses idle watts",
dev: Device{WattsIdle: 65, WattsTypical: 200, WattsPeak: 450, DutyCycle: 1.0},
mode: ModeIdle,
want: 65,
},
{
name: "typical mode with full duty cycle",
dev: Device{WattsIdle: 65, WattsTypical: 200, WattsPeak: 450, DutyCycle: 1.0},
mode: ModeTypical,
want: 200,
},
{
name: "typical mode with 50% duty cycle",
dev: Device{WattsIdle: 65, WattsTypical: 200, WattsPeak: 450, DutyCycle: 0.5},
mode: ModeTypical,
want: 100,
},
{
name: "peak mode",
dev: Device{WattsIdle: 65, WattsTypical: 200, WattsPeak: 450, DutyCycle: 1.0},
mode: ModePeak,
want: 450,
},
{
name: "zero duty cycle",
dev: Device{WattsIdle: 65, WattsTypical: 200, WattsPeak: 450, DutyCycle: 0.0},
mode: ModeTypical,
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := DeviceHeatGain(tt.dev, tt.mode)
if !almostEqual(got, tt.want, tolerance) {
t.Errorf("DeviceHeatGain() = %v, want %v", got, tt.want)
}
})
}
}
func TestOccupantHeatGain(t *testing.T) {
tests := []struct {
name string
count int
activity ActivityLevel
want float64
}{
{"one sleeping person", 1, Sleeping, 70},
{"one sedentary person", 1, Sedentary, 100},
{"two sedentary people", 2, Sedentary, 200},
{"one light activity", 1, LightActivity, 130},
{"one moderate activity", 1, ModerateActivity, 200},
{"one heavy activity", 1, HeavyActivity, 300},
{"zero people", 0, Sedentary, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := OccupantHeatGain(tt.count, tt.activity)
if !almostEqual(got, tt.want, tolerance) {
t.Errorf("OccupantHeatGain(%d, %v) = %v, want %v", tt.count, tt.activity, got, tt.want)
}
})
}
}
func TestTotalInternalGains(t *testing.T) {
devices := []Device{
{WattsIdle: 65, WattsTypical: 200, WattsPeak: 450, DutyCycle: 1.0},
{WattsIdle: 30, WattsTypical: 80, WattsPeak: 120, DutyCycle: 0.5},
}
occupants := []Occupant{
{Count: 1, Activity: Sedentary},
{Count: 2, Activity: LightActivity},
}
// 200 + 40 + 100 + 260 = 600
got := TotalInternalGains(devices, ModeTypical, occupants)
want := 600.0
if !almostEqual(got, want, tolerance) {
t.Errorf("TotalInternalGains() = %v, want %v", got, want)
}
}