import pytest from unittest.mock import AsyncMock, patch, MagicMock def test_parse_open_meteo_response(): """parse_forecast() should extract daily data into a clean list.""" from fabledassistant.services.weather import parse_forecast raw = { "daily": { "time": ["2026-03-11", "2026-03-12"], "temperature_2m_max": [15.0, 12.0], "temperature_2m_min": [8.0, 6.0], "precipitation_sum": [0.0, 3.2], "weathercode": [0, 61], "windspeed_10m_max": [10.0, 25.0], } } days = parse_forecast(raw) assert len(days) == 2 assert days[0]["date"] == "2026-03-11" assert days[0]["temp_max"] == 15.0 assert days[0]["temp_min"] == 8.0 assert days[0]["precip_mm"] == 0.0 assert days[0]["weathercode"] == 0 assert days[1]["date"] == "2026-03-12" assert days[1]["precip_mm"] == 3.2 def test_describe_weathercode(): """describe_weathercode() should return human-readable strings.""" from fabledassistant.services.weather import describe_weathercode assert describe_weathercode(0) == "Clear sky" assert describe_weathercode(61) == "Rain: slight" assert describe_weathercode(95) == "Thunderstorm" assert "Unknown" in describe_weathercode(999) def test_detect_forecast_changes_no_change(): """detect_changes() should return empty list when forecasts are identical.""" from fabledassistant.services.weather import detect_changes day = {"date": "2026-03-12", "temp_max": 12.0, "temp_min": 6.0, "precip_mm": 3.2, "weathercode": 61} assert detect_changes([day], [day]) == [] def test_detect_forecast_changes_rain_added(): """detect_changes() should flag when precipitation appears.""" from fabledassistant.services.weather import detect_changes old = [{"date": "2026-03-12", "temp_max": 14.0, "temp_min": 8.0, "precip_mm": 0.0, "weathercode": 2}] new = [{"date": "2026-03-12", "temp_max": 12.0, "temp_min": 7.0, "precip_mm": 4.5, "weathercode": 61}] changes = detect_changes(old, new) assert len(changes) == 1 assert "2026-03-12" in changes[0] assert "rain" in changes[0].lower() or "precip" in changes[0].lower() def test_parse_weather_card_data_returns_none_when_stale(): """Should return None when cache is older than 24 hours.""" from datetime import datetime, timezone, timedelta from unittest.mock import MagicMock from fabledassistant.services.weather import parse_weather_card_data cache = MagicMock() cache.fetched_at = datetime.now(timezone.utc) - timedelta(hours=25) cache.forecast_json = {} assert parse_weather_card_data(cache) is None def test_parse_weather_card_data_returns_card_schema(): """Should return the correct metadata.weather schema for fresh cache.""" from datetime import datetime, timezone, timedelta, date from unittest.mock import MagicMock from fabledassistant.services.weather import parse_weather_card_data today = date.today().isoformat() yesterday = (date.today() - timedelta(days=1)).isoformat() tomorrow = (date.today() + timedelta(days=1)).isoformat() cache = MagicMock() cache.fetched_at = datetime.now(timezone.utc) cache.location_label = "Berlin, DE" cache.forecast_json = { "current_weather": {"temperature": 12.0, "weathercode": 2}, "daily": { "time": [yesterday, today, tomorrow], "temperature_2m_max": [14.0, 16.0, 18.0], "temperature_2m_min": [9.0, 8.0, 10.0], "precipitation_sum": [0.0, 0.0, 1.5], "weathercode": [1, 2, 61], "windspeed_10m_max": [10.0, 12.0, 8.0], } } card = parse_weather_card_data(cache) assert card is not None assert card["current_temp"] == 12 assert card["condition"] == "Partly cloudy" assert card["today_high"] == 16 assert card["today_low"] == 8 assert card["yesterday_high"] == 14 assert card["yesterday_low"] == 9 assert len(card["forecast"]) >= 1 assert card["location"] == "Berlin, DE"