from unittest.mock import patch from app.homelab.openmeteo import ( PRECIP_PROB_HINT, RECOMMENDED_SYNC_DOMAINS, RECOMMENDED_SYNC_VARIABLES, SYNC_HINT, _coverage_sufficient, _field_coverage, _hourly_start_index, _local_needs_sync_hint, build_weather_dashboard, format_weather_snapshot, weather_query_relevant, ) def test_hourly_start_index_from_current(): times = ["2026-06-14T00:00", "2026-06-14T01:00", "2026-06-14T18:00", "2026-06-14T19:00"] assert _hourly_start_index(times, "2026-06-14T18:15") == 2 def test_coverage_sufficient(): assert _coverage_sufficient({"current": ["temperature_2m"], "hourly": ["temperature_2m"]}) is False assert _coverage_sufficient( { "current": ["temperature_2m", "weather_code", "wind_speed_10m"], "hourly": ["temperature_2m", "weather_code"], } ) is True def test_field_coverage_partial(): raw = { "current": {"time": "2026-06-14T18:15", "temperature_2m": 20.6, "weather_code": 2}, "hourly": { "time": ["2026-06-14T18:00", "2026-06-14T19:00"], "temperature_2m": [20.0, 19.5], "precipitation": [0.0, 0.0], "weather_code": [2, 3], }, "daily": { "time": ["2026-06-14", "2026-06-15"], "temperature_2m_max": [21.0, 18.0], "temperature_2m_min": [12.0, 10.0], "weather_code": [2, 3], }, } coverage = _field_coverage(raw) assert "temperature_2m" in coverage["current"] assert "weather_code" in coverage["hourly"] assert "temperature_2m_max" in coverage["daily"] def test_local_needs_sync_hint(): assert _local_needs_sync_hint({"current": ["temperature_2m"], "hourly": ["temperature_2m"]}) is True assert _local_needs_sync_hint( {"current": ["temperature_2m", "weather_code"], "hourly": ["temperature_2m", "weather_code"]} ) is False def test_weather_query_relevant(): assert weather_query_relevant("какая погода завтра") assert not weather_query_relevant("напиши код на python") def test_format_weather_snapshot_includes_tomorrow(): snap = { "ok": True, "location": "СПб", "current": {"temperature_c": 20, "conditions": "ясно"}, "hourly": [], "daily": [ {"label": "Сегодня", "temperature_min_c": 10, "temperature_max_c": 20, "conditions": "ясно"}, {"label": "Завтра", "temperature_min_c": 12, "temperature_max_c": 18, "conditions": "дождь"}, ], } text = format_weather_snapshot(snap) assert "Завтра:" in text assert "None" not in text def test_build_weather_dashboard_includes_daily(): fake_weather = { "ok": True, "location": "Test", "data_source": "local", "local_field_coverage": {"current": ["temperature_2m", "weather_code"], "hourly": ["temperature_2m"], "daily": []}, "field_coverage": {"current": ["temperature_2m"], "hourly": ["temperature_2m"], "daily": []}, "sync_hint": PRECIP_PROB_HINT, "merged_fields": [], "current": {"temperature_c": 10, "conditions": "ясно"}, "hourly": [], "daily": [{"label": "Завтра", "temperature_min_c": 5, "temperature_max_c": 12, "conditions": "дождь"}], } with patch("app.homelab.openmeteo.OpenMeteoClient") as mock_cls: client = mock_cls.return_value client.fetch_forecast.return_value = fake_weather client.rain_summary.return_value = "ok" client.daily_summary.return_value = "Завтра: 5–12°C" client.cache_status.return_value = {"source": "local", "has_data": True, "cached": True, "ttl_sec": 300} client.location_name = "Test" client.lat = 1.0 client.lon = 2.0 client.base_url = "http://local" client.cache_ttl = 300 client.forecast_days = 7 result = build_weather_dashboard(days_ahead=7) assert result["daily_summary"] == "Завтра: 5–12°C" assert result["recommended_sync"]["domains"] == RECOMMENDED_SYNC_DOMAINS assert result["recommended_sync"]["variables"] == RECOMMENDED_SYNC_VARIABLES assert SYNC_HINT # constant exists