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

89 lines
2.5 KiB
Go

package weather
import (
"context"
"net/http"
"net/http/httptest"
"testing"
)
const openMeteoTestJSON = `{
"hourly": {
"time": ["2025-07-15T00:00", "2025-07-15T01:00", "2025-07-15T02:00"],
"temperature_2m": [22.5, 21.8, 21.0],
"apparent_temperature": [23.1, 22.4, 21.5],
"relative_humidity_2m": [65, 68, 72],
"dew_point_2m": [15.5, 15.8, 16.0],
"cloud_cover": [20, 30, 45],
"wind_speed_10m": [3.5, 2.8, 2.1],
"wind_direction_10m": [180, 190, 200],
"precipitation": [0, 0, 0],
"sunshine_duration": [3600, 3000, 0],
"shortwave_radiation": [0, 0, 0],
"surface_pressure": [1013, 1013, 1012],
"is_day": [0, 0, 0]
},
"daily": {
"time": ["2025-07-15"],
"temperature_2m_max": [35.5],
"temperature_2m_min": [20.1],
"apparent_temperature_max": [37.2],
"sunrise": ["2025-07-15T05:15"],
"sunset": ["2025-07-15T21:30"]
}
}`
func TestOpenMeteoFetchForecast(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(openMeteoTestJSON))
}))
defer srv.Close()
om := NewOpenMeteo(srv.Client())
om.baseURL = srv.URL
resp, err := om.FetchForecast(context.Background(), 52.52, 13.41, "Europe/Berlin")
if err != nil {
t.Fatalf("FetchForecast: %v", err)
}
if resp.Source != "openmeteo" {
t.Errorf("Source = %s, want openmeteo", resp.Source)
}
if len(resp.Hourly) != 3 {
t.Fatalf("Hourly len = %d, want 3", len(resp.Hourly))
}
if resp.Hourly[0].TemperatureC != 22.5 {
t.Errorf("Hourly[0].TemperatureC = %v, want 22.5", resp.Hourly[0].TemperatureC)
}
if resp.Hourly[0].ApparentTempC != 23.1 {
t.Errorf("Hourly[0].ApparentTempC = %v, want 23.1", resp.Hourly[0].ApparentTempC)
}
if resp.Hourly[0].SunshineMin != 60.0 {
t.Errorf("Hourly[0].SunshineMin = %v, want 60 (3600s / 60)", resp.Hourly[0].SunshineMin)
}
if len(resp.Daily) != 1 {
t.Fatalf("Daily len = %d, want 1", len(resp.Daily))
}
if resp.Daily[0].TempMaxC != 35.5 {
t.Errorf("Daily[0].TempMaxC = %v, want 35.5", resp.Daily[0].TempMaxC)
}
}
func TestOpenMeteoServerError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer srv.Close()
om := NewOpenMeteo(srv.Client())
om.baseURL = srv.URL
_, err := om.FetchForecast(context.Background(), 52.52, 13.41, "Europe/Berlin")
if err == nil {
t.Error("expected error for 500 response")
}
}