From 6645932fb7a98bd89208e1810df4071c7f743ff8 Mon Sep 17 00:00:00 2001
From: Glenn Waters <glenn@watrs.ca>
Date: Tue, 16 Jul 2024 05:57:37 -0400
Subject: [PATCH] Fix for Environment Canada date being wrong after midnight
 (#121850)

* Use async_connect in newly bumped 0.5.8 UPB library.

* Fix tests.

* Fix date being wrong after midnight for Environment Canada

* Fix typing.

* Add test.

* Formatting.

* Remove tests until can be added properly.

* Add weather tests back.

* Fix tests

* Change of tactic for determining previous day's data.

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
---
 .../environment_canada/manifest.json          |  2 +-
 .../components/environment_canada/weather.py  | 68 ++++----------
 requirements_all.txt                          |  2 +-
 requirements_test_all.txt                     |  2 +-
 .../components/environment_canada/__init__.py | 66 +++++++++++++
 .../fixtures/current_conditions_data.json     | 36 ++++---
 .../snapshots/test_weather.ambr               | 94 +++++++++++++++++++
 .../environment_canada/test_diagnostics.py    | 68 ++------------
 .../environment_canada/test_weather.py        | 68 ++++++++++++++
 9 files changed, 282 insertions(+), 124 deletions(-)
 create mode 100644 tests/components/environment_canada/snapshots/test_weather.ambr
 create mode 100644 tests/components/environment_canada/test_weather.py

diff --git a/homeassistant/components/environment_canada/manifest.json b/homeassistant/components/environment_canada/manifest.json
index c77d35b1769..76534662ff7 100644
--- a/homeassistant/components/environment_canada/manifest.json
+++ b/homeassistant/components/environment_canada/manifest.json
@@ -6,5 +6,5 @@
   "documentation": "https://www.home-assistant.io/integrations/environment_canada",
   "iot_class": "cloud_polling",
   "loggers": ["env_canada"],
-  "requirements": ["env-canada==0.7.1"]
+  "requirements": ["env-canada==0.7.2"]
 }
diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py
index a3036c55659..2d54a313dde 100644
--- a/homeassistant/components/environment_canada/weather.py
+++ b/homeassistant/components/environment_canada/weather.py
@@ -2,8 +2,6 @@
 
 from __future__ import annotations
 
-import datetime
-
 from homeassistant.components.weather import (
     ATTR_CONDITION_CLEAR_NIGHT,
     ATTR_CONDITION_CLOUDY,
@@ -37,7 +35,6 @@ from homeassistant.const import (
 from homeassistant.core import HomeAssistant, callback
 from homeassistant.helpers import entity_registry as er
 from homeassistant.helpers.entity_platform import AddEntitiesCallback
-from homeassistant.util import dt as dt_util
 
 from . import device_info
 from .const import DOMAIN
@@ -193,53 +190,24 @@ def get_forecast(ec_data, hourly) -> list[Forecast] | None:
         if not (half_days := ec_data.daily_forecasts):
             return None
 
-        today: Forecast = {
-            ATTR_FORECAST_TIME: dt_util.now().isoformat(),
-            ATTR_FORECAST_CONDITION: icon_code_to_condition(
-                int(half_days[0]["icon_code"])
-            ),
-            ATTR_FORECAST_PRECIPITATION_PROBABILITY: int(
-                half_days[0]["precip_probability"]
-            ),
-        }
-
-        if half_days[0]["temperature_class"] == "high":
-            today.update(
-                {
-                    ATTR_FORECAST_NATIVE_TEMP: int(half_days[0]["temperature"]),
-                    ATTR_FORECAST_NATIVE_TEMP_LOW: int(half_days[1]["temperature"]),
-                }
-            )
-            half_days = half_days[2:]
-        else:
-            today.update(
-                {
-                    ATTR_FORECAST_NATIVE_TEMP: None,
-                    ATTR_FORECAST_NATIVE_TEMP_LOW: int(half_days[0]["temperature"]),
-                }
-            )
-            half_days = half_days[1:]
-
-        forecast_array.append(today)
-
-        for day, high, low in zip(
-            range(1, 6), range(0, 9, 2), range(1, 10, 2), strict=False
-        ):
-            forecast_array.append(
-                {
-                    ATTR_FORECAST_TIME: (
-                        dt_util.now() + datetime.timedelta(days=day)
-                    ).isoformat(),
-                    ATTR_FORECAST_NATIVE_TEMP: int(half_days[high]["temperature"]),
-                    ATTR_FORECAST_NATIVE_TEMP_LOW: int(half_days[low]["temperature"]),
-                    ATTR_FORECAST_CONDITION: icon_code_to_condition(
-                        int(half_days[high]["icon_code"])
-                    ),
-                    ATTR_FORECAST_PRECIPITATION_PROBABILITY: int(
-                        half_days[high]["precip_probability"]
-                    ),
-                }
-            )
+        def get_day_forecast(fcst: list[dict[str, str]]) -> Forecast:
+            high_temp = int(fcst[0]["temperature"]) if len(fcst) == 2 else None
+            return {
+                ATTR_FORECAST_TIME: fcst[0]["timestamp"],
+                ATTR_FORECAST_NATIVE_TEMP: high_temp,
+                ATTR_FORECAST_NATIVE_TEMP_LOW: int(fcst[-1]["temperature"]),
+                ATTR_FORECAST_PRECIPITATION_PROBABILITY: int(
+                    fcst[0]["precip_probability"]
+                ),
+                ATTR_FORECAST_CONDITION: icon_code_to_condition(
+                    int(fcst[0]["icon_code"])
+                ),
+            }
+
+        i = 2 if half_days[0]["temperature_class"] == "high" else 1
+        forecast_array.append(get_day_forecast(half_days[0:i]))
+        for i in range(i, len(half_days) - 1, 2):
+            forecast_array.append(get_day_forecast(half_days[i : i + 2]))  # noqa: PERF401
 
     else:
         forecast_array.extend(
diff --git a/requirements_all.txt b/requirements_all.txt
index 70dc90eed6f..c3d9d0f72d2 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -813,7 +813,7 @@ enocean==0.50
 enturclient==0.2.4
 
 # homeassistant.components.environment_canada
-env-canada==0.7.1
+env-canada==0.7.2
 
 # homeassistant.components.season
 ephem==4.1.5
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 568528951d5..2b16427cb0a 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -682,7 +682,7 @@ energyzero==2.1.1
 enocean==0.50
 
 # homeassistant.components.environment_canada
-env-canada==0.7.1
+env-canada==0.7.2
 
 # homeassistant.components.season
 ephem==4.1.5
diff --git a/tests/components/environment_canada/__init__.py b/tests/components/environment_canada/__init__.py
index 65b0ed16207..92c28e09b74 100644
--- a/tests/components/environment_canada/__init__.py
+++ b/tests/components/environment_canada/__init__.py
@@ -1 +1,67 @@
 """Tests for the Environment Canada integration."""
+
+from datetime import UTC, datetime
+from unittest.mock import AsyncMock, MagicMock, patch
+
+from homeassistant.components.environment_canada.const import CONF_STATION, DOMAIN
+from homeassistant.const import CONF_LANGUAGE, CONF_LATITUDE, CONF_LONGITUDE
+from homeassistant.core import HomeAssistant
+
+from tests.common import MockConfigEntry
+
+FIXTURE_USER_INPUT = {
+    CONF_LATITUDE: 55.55,
+    CONF_LONGITUDE: 42.42,
+    CONF_STATION: "XX/1234567",
+    CONF_LANGUAGE: "Gibberish",
+}
+
+
+async def init_integration(hass: HomeAssistant, ec_data) -> MockConfigEntry:
+    """Set up the Environment Canada integration in Home Assistant."""
+
+    def mock_ec():
+        ec_mock = MagicMock()
+        ec_mock.station_id = FIXTURE_USER_INPUT[CONF_STATION]
+        ec_mock.lat = FIXTURE_USER_INPUT[CONF_LATITUDE]
+        ec_mock.lon = FIXTURE_USER_INPUT[CONF_LONGITUDE]
+        ec_mock.language = FIXTURE_USER_INPUT[CONF_LANGUAGE]
+        ec_mock.update = AsyncMock()
+        return ec_mock
+
+    config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT, title="Home")
+    config_entry.add_to_hass(hass)
+
+    weather_mock = mock_ec()
+    ec_data["metadata"]["timestamp"] = datetime(2022, 10, 4, tzinfo=UTC)
+    weather_mock.conditions = ec_data["conditions"]
+    weather_mock.alerts = ec_data["alerts"]
+    weather_mock.daily_forecasts = ec_data["daily_forecasts"]
+    weather_mock.metadata = ec_data["metadata"]
+
+    radar_mock = mock_ec()
+    radar_mock.image = b"GIF..."
+    radar_mock.timestamp = datetime(2022, 10, 4, tzinfo=UTC)
+
+    with (
+        patch(
+            "homeassistant.components.environment_canada.ECWeather",
+            return_value=weather_mock,
+        ),
+        patch(
+            "homeassistant.components.environment_canada.ECAirQuality",
+            return_value=mock_ec(),
+        ),
+        patch(
+            "homeassistant.components.environment_canada.ECRadar",
+            return_value=radar_mock,
+        ),
+        patch(
+            "homeassistant.components.environment_canada.config_flow.ECWeather",
+            return_value=weather_mock,
+        ),
+    ):
+        await hass.config_entries.async_setup(config_entry.entry_id)
+        await hass.async_block_till_done()
+
+    return config_entry
diff --git a/tests/components/environment_canada/fixtures/current_conditions_data.json b/tests/components/environment_canada/fixtures/current_conditions_data.json
index f3a18869940..ceb00028f95 100644
--- a/tests/components/environment_canada/fixtures/current_conditions_data.json
+++ b/tests/components/environment_canada/fixtures/current_conditions_data.json
@@ -135,7 +135,8 @@
       "icon_code": "30",
       "temperature": -1,
       "temperature_class": "low",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-03 15:00:00+00:00"
     },
     {
       "period": "Tuesday",
@@ -143,7 +144,8 @@
       "icon_code": "00",
       "temperature": 18,
       "temperature_class": "high",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-04 15:00:00+00:00"
     },
     {
       "period": "Tuesday night",
@@ -151,7 +153,8 @@
       "icon_code": "30",
       "temperature": 3,
       "temperature_class": "low",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-04 15:00:00+00:00"
     },
     {
       "period": "Wednesday",
@@ -159,7 +162,8 @@
       "icon_code": "00",
       "temperature": 20,
       "temperature_class": "high",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-05 15:00:00+00:00"
     },
     {
       "period": "Wednesday night",
@@ -167,7 +171,8 @@
       "icon_code": "30",
       "temperature": 9,
       "temperature_class": "low",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-05 15:00:00+00:00"
     },
     {
       "period": "Thursday",
@@ -175,7 +180,8 @@
       "icon_code": "02",
       "temperature": 20,
       "temperature_class": "high",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-06 15:00:00+00:00"
     },
     {
       "period": "Thursday night",
@@ -183,7 +189,8 @@
       "icon_code": "12",
       "temperature": 7,
       "temperature_class": "low",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-06 15:00:00+00:00"
     },
     {
       "period": "Friday",
@@ -191,7 +198,8 @@
       "icon_code": "12",
       "temperature": 13,
       "temperature_class": "high",
-      "precip_probability": 40
+      "precip_probability": 40,
+      "timestamp": "2022-10-07 15:00:00+00:00"
     },
     {
       "period": "Friday night",
@@ -199,7 +207,8 @@
       "icon_code": "32",
       "temperature": 1,
       "temperature_class": "low",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-07 15:00:00+00:00"
     },
     {
       "period": "Saturday",
@@ -207,7 +216,8 @@
       "icon_code": "02",
       "temperature": 10,
       "temperature_class": "high",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-08 15:00:00+00:00"
     },
     {
       "period": "Saturday night",
@@ -215,7 +225,8 @@
       "icon_code": "32",
       "temperature": 3,
       "temperature_class": "low",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-08 15:00:00+00:00"
     },
     {
       "period": "Sunday",
@@ -223,7 +234,8 @@
       "icon_code": "02",
       "temperature": 12,
       "temperature_class": "high",
-      "precip_probability": 0
+      "precip_probability": 0,
+      "timestamp": "2022-10-09 15:00:00+00:00"
     }
   ],
   "metadata": {
diff --git a/tests/components/environment_canada/snapshots/test_weather.ambr b/tests/components/environment_canada/snapshots/test_weather.ambr
new file mode 100644
index 00000000000..7ba37110c2a
--- /dev/null
+++ b/tests/components/environment_canada/snapshots/test_weather.ambr
@@ -0,0 +1,94 @@
+# serializer version: 1
+# name: test_forecast_daily
+  dict({
+    'weather.home_forecast': dict({
+      'forecast': list([
+        dict({
+          'condition': 'sunny',
+          'datetime': '2022-10-04 15:00:00+00:00',
+          'precipitation_probability': 0,
+          'temperature': 18.0,
+          'templow': 3.0,
+        }),
+        dict({
+          'condition': 'sunny',
+          'datetime': '2022-10-05 15:00:00+00:00',
+          'precipitation_probability': 0,
+          'temperature': 20.0,
+          'templow': 9.0,
+        }),
+        dict({
+          'condition': 'partlycloudy',
+          'datetime': '2022-10-06 15:00:00+00:00',
+          'precipitation_probability': 0,
+          'temperature': 20.0,
+          'templow': 7.0,
+        }),
+        dict({
+          'condition': 'rainy',
+          'datetime': '2022-10-07 15:00:00+00:00',
+          'precipitation_probability': 40,
+          'temperature': 13.0,
+          'templow': 1.0,
+        }),
+        dict({
+          'condition': 'partlycloudy',
+          'datetime': '2022-10-08 15:00:00+00:00',
+          'precipitation_probability': 0,
+          'temperature': 10.0,
+          'templow': 3.0,
+        }),
+      ]),
+    }),
+  })
+# ---
+# name: test_forecast_daily_with_some_previous_days_data
+  dict({
+    'weather.home_forecast': dict({
+      'forecast': list([
+        dict({
+          'condition': 'clear-night',
+          'datetime': '2022-10-03 15:00:00+00:00',
+          'precipitation_probability': 0,
+          'temperature': None,
+          'templow': -1.0,
+        }),
+        dict({
+          'condition': 'sunny',
+          'datetime': '2022-10-04 15:00:00+00:00',
+          'precipitation_probability': 0,
+          'temperature': 18.0,
+          'templow': 3.0,
+        }),
+        dict({
+          'condition': 'sunny',
+          'datetime': '2022-10-05 15:00:00+00:00',
+          'precipitation_probability': 0,
+          'temperature': 20.0,
+          'templow': 9.0,
+        }),
+        dict({
+          'condition': 'partlycloudy',
+          'datetime': '2022-10-06 15:00:00+00:00',
+          'precipitation_probability': 0,
+          'temperature': 20.0,
+          'templow': 7.0,
+        }),
+        dict({
+          'condition': 'rainy',
+          'datetime': '2022-10-07 15:00:00+00:00',
+          'precipitation_probability': 40,
+          'temperature': 13.0,
+          'templow': 1.0,
+        }),
+        dict({
+          'condition': 'partlycloudy',
+          'datetime': '2022-10-08 15:00:00+00:00',
+          'precipitation_probability': 0,
+          'temperature': 10.0,
+          'templow': 3.0,
+        }),
+      ]),
+    }),
+  })
+# ---
diff --git a/tests/components/environment_canada/test_diagnostics.py b/tests/components/environment_canada/test_diagnostics.py
index 8f800111d39..7e9c8691f90 100644
--- a/tests/components/environment_canada/test_diagnostics.py
+++ b/tests/components/environment_canada/test_diagnostics.py
@@ -1,16 +1,16 @@
 """Test Environment Canada diagnostics."""
 
-from datetime import UTC, datetime
 import json
-from unittest.mock import AsyncMock, MagicMock, patch
 
 from syrupy import SnapshotAssertion
 
-from homeassistant.components.environment_canada.const import CONF_STATION, DOMAIN
+from homeassistant.components.environment_canada.const import CONF_STATION
 from homeassistant.const import CONF_LANGUAGE, CONF_LATITUDE, CONF_LONGITUDE
 from homeassistant.core import HomeAssistant
 
-from tests.common import MockConfigEntry, load_fixture
+from . import init_integration
+
+from tests.common import load_fixture
 from tests.components.diagnostics import get_diagnostics_for_config_entry
 from tests.typing import ClientSessionGenerator
 
@@ -22,60 +22,6 @@ FIXTURE_USER_INPUT = {
 }
 
 
-async def init_integration(hass: HomeAssistant) -> MockConfigEntry:
-    """Set up the Environment Canada integration in Home Assistant."""
-
-    def mock_ec():
-        ec_mock = MagicMock()
-        ec_mock.station_id = FIXTURE_USER_INPUT[CONF_STATION]
-        ec_mock.lat = FIXTURE_USER_INPUT[CONF_LATITUDE]
-        ec_mock.lon = FIXTURE_USER_INPUT[CONF_LONGITUDE]
-        ec_mock.language = FIXTURE_USER_INPUT[CONF_LANGUAGE]
-        ec_mock.update = AsyncMock()
-        return ec_mock
-
-    config_entry = MockConfigEntry(domain=DOMAIN, data=FIXTURE_USER_INPUT)
-    config_entry.add_to_hass(hass)
-
-    ec_data = json.loads(
-        load_fixture("environment_canada/current_conditions_data.json")
-    )
-
-    weather_mock = mock_ec()
-    ec_data["metadata"]["timestamp"] = datetime(2022, 10, 4, tzinfo=UTC)
-    weather_mock.conditions = ec_data["conditions"]
-    weather_mock.alerts = ec_data["alerts"]
-    weather_mock.daily_forecasts = ec_data["daily_forecasts"]
-    weather_mock.metadata = ec_data["metadata"]
-
-    radar_mock = mock_ec()
-    radar_mock.image = b"GIF..."
-    radar_mock.timestamp = datetime(2022, 10, 4, tzinfo=UTC)
-
-    with (
-        patch(
-            "homeassistant.components.environment_canada.ECWeather",
-            return_value=weather_mock,
-        ),
-        patch(
-            "homeassistant.components.environment_canada.ECAirQuality",
-            return_value=mock_ec(),
-        ),
-        patch(
-            "homeassistant.components.environment_canada.ECRadar",
-            return_value=radar_mock,
-        ),
-        patch(
-            "homeassistant.components.environment_canada.config_flow.ECWeather",
-            return_value=weather_mock,
-        ),
-    ):
-        await hass.config_entries.async_setup(config_entry.entry_id)
-        await hass.async_block_till_done()
-
-    return config_entry
-
-
 async def test_entry_diagnostics(
     hass: HomeAssistant,
     hass_client: ClientSessionGenerator,
@@ -83,7 +29,11 @@ async def test_entry_diagnostics(
 ) -> None:
     """Test config entry diagnostics."""
 
-    config_entry = await init_integration(hass)
+    ec_data = json.loads(
+        load_fixture("environment_canada/current_conditions_data.json")
+    )
+
+    config_entry = await init_integration(hass, ec_data)
     diagnostics = await get_diagnostics_for_config_entry(
         hass, hass_client, config_entry
     )
diff --git a/tests/components/environment_canada/test_weather.py b/tests/components/environment_canada/test_weather.py
new file mode 100644
index 00000000000..e8c21e2dc06
--- /dev/null
+++ b/tests/components/environment_canada/test_weather.py
@@ -0,0 +1,68 @@
+"""Test weather."""
+
+import json
+
+from syrupy.assertion import SnapshotAssertion
+
+from homeassistant.components.weather import (
+    DOMAIN as WEATHER_DOMAIN,
+    SERVICE_GET_FORECASTS,
+)
+from homeassistant.core import HomeAssistant
+
+from . import init_integration
+
+from tests.common import load_fixture
+
+
+async def test_forecast_daily(
+    hass: HomeAssistant,
+    snapshot: SnapshotAssertion,
+) -> None:
+    """Test basic forecast."""
+
+    ec_data = json.loads(
+        load_fixture("environment_canada/current_conditions_data.json")
+    )
+
+    # First entry in test data is a half day; we don't want that for this test
+    del ec_data["daily_forecasts"][0]
+
+    await init_integration(hass, ec_data)
+
+    response = await hass.services.async_call(
+        WEATHER_DOMAIN,
+        SERVICE_GET_FORECASTS,
+        {
+            "entity_id": "weather.home_forecast",
+            "type": "daily",
+        },
+        blocking=True,
+        return_response=True,
+    )
+    assert response == snapshot
+
+
+async def test_forecast_daily_with_some_previous_days_data(
+    hass: HomeAssistant,
+    snapshot: SnapshotAssertion,
+) -> None:
+    """Test forecast with half day at start."""
+
+    ec_data = json.loads(
+        load_fixture("environment_canada/current_conditions_data.json")
+    )
+
+    await init_integration(hass, ec_data)
+
+    response = await hass.services.async_call(
+        WEATHER_DOMAIN,
+        SERVICE_GET_FORECASTS,
+        {
+            "entity_id": "weather.home_forecast",
+            "type": "daily",
+        },
+        blocking=True,
+        return_response=True,
+    )
+    assert response == snapshot
-- 
GitLab