Files
HeatGuard/internal/weather/brightsky_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

93 lines
2.4 KiB
Go

package weather
import (
"context"
"net/http"
"net/http/httptest"
"testing"
)
const brightSkyTestJSON = `{
"weather": [
{
"timestamp": "2025-07-15T14:00:00+02:00",
"temperature": 34.5,
"relative_humidity": 40,
"dew_point": 19.5,
"cloud_cover": 15,
"wind_speed": 10.8,
"wind_direction": 220,
"precipitation": 0,
"sunshine": 55,
"pressure_msl": 1015.2,
"condition": "dry"
},
{
"timestamp": "2025-07-15T15:00:00+02:00",
"temperature": 35.1,
"relative_humidity": 38,
"dew_point": 19.0,
"cloud_cover": 10,
"wind_speed": 7.2,
"wind_direction": 210,
"precipitation": 0,
"sunshine": 60,
"pressure_msl": 1015.0,
"condition": "dry"
}
]
}`
func TestBrightSkyFetchForecast(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(brightSkyTestJSON))
}))
defer srv.Close()
bs := NewBrightSky(srv.Client())
bs.baseURL = srv.URL
resp, err := bs.FetchForecast(context.Background(), 52.52, 13.41, "Europe/Berlin")
if err != nil {
t.Fatalf("FetchForecast: %v", err)
}
if resp.Source != "brightsky" {
t.Errorf("Source = %s, want brightsky", resp.Source)
}
if len(resp.Hourly) != 2 {
t.Fatalf("Hourly len = %d, want 2", len(resp.Hourly))
}
if resp.Hourly[0].TemperatureC != 34.5 {
t.Errorf("temp = %v, want 34.5", resp.Hourly[0].TemperatureC)
}
// wind_speed is km/h in brightsky, converted to m/s
expectedWindMs := 10.8 / 3.6
if diff := resp.Hourly[0].WindSpeedMs - expectedWindMs; diff > 0.01 || diff < -0.01 {
t.Errorf("WindSpeedMs = %v, want %v", resp.Hourly[0].WindSpeedMs, expectedWindMs)
}
if resp.Hourly[0].Condition != "dry" {
t.Errorf("Condition = %s, want dry", resp.Hourly[0].Condition)
}
// 14:00 should be daytime
if !resp.Hourly[0].IsDay {
t.Error("expected IsDay=true for hour 14")
}
}
func TestBrightSkyServerError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadGateway)
}))
defer srv.Close()
bs := NewBrightSky(srv.Client())
bs.baseURL = srv.URL
_, err := bs.FetchForecast(context.Background(), 52.52, 13.41, "Europe/Berlin")
if err == nil {
t.Error("expected error for 502 response")
}
}