From ab1df8065c510015fd2021ad93eae2c3eaa92227 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 6 Mar 2023 10:26:37 +0100 Subject: [PATCH] Refresh homeassistant_alerts when components are loaded (#76049) --- .../homeassistant_alerts/__init__.py | 18 +- .../homeassistant_alerts/test_init.py | 233 +++++++++++++++++- 2 files changed, 247 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homeassistant_alerts/__init__.py b/homeassistant/components/homeassistant_alerts/__init__.py index 7012111ed61..ffc0594baf3 100644 --- a/homeassistant/components/homeassistant_alerts/__init__.py +++ b/homeassistant/components/homeassistant_alerts/__init__.py @@ -10,9 +10,10 @@ import aiohttp from awesomeversion import AwesomeVersion, AwesomeVersionStrategy from homeassistant.components.hassio import get_supervisor_info, is_hassio -from homeassistant.const import __version__ -from homeassistant.core import HomeAssistant, callback +from homeassistant.const import EVENT_COMPONENT_LOADED, __version__ +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.issue_registry import ( IssueSeverity, async_create_issue, @@ -22,6 +23,7 @@ from homeassistant.helpers.start import async_at_start from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +COMPONENT_LOADED_COOLDOWN = 30 DOMAIN = "homeassistant_alerts" UPDATE_INTERVAL = timedelta(hours=3) _LOGGER = logging.getLogger(__name__) @@ -85,7 +87,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: coordinator.async_add_listener(async_schedule_update_alerts) async def initial_refresh(hass: HomeAssistant) -> None: + refresh_debouncer = Debouncer( + hass, + _LOGGER, + cooldown=COMPONENT_LOADED_COOLDOWN, + immediate=False, + function=coordinator.async_refresh, + ) + + async def _component_loaded(_: Event) -> None: + await refresh_debouncer.async_call() + await coordinator.async_refresh() + hass.bus.async_listen(EVENT_COMPONENT_LOADED, _component_loaded) async_at_start(hass, initial_refresh) diff --git a/tests/components/homeassistant_alerts/test_init.py b/tests/components/homeassistant_alerts/test_init.py index 9bb54bd56af..f5e040aa389 100644 --- a/tests/components/homeassistant_alerts/test_init.py +++ b/tests/components/homeassistant_alerts/test_init.py @@ -7,10 +7,15 @@ from unittest.mock import ANY, patch import pytest -from homeassistant.components.homeassistant_alerts import DOMAIN, UPDATE_INTERVAL +from homeassistant.components.homeassistant_alerts import ( + COMPONENT_LOADED_COOLDOWN, + DOMAIN, + UPDATE_INTERVAL, +) from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN +from homeassistant.const import EVENT_COMPONENT_LOADED from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component +from homeassistant.setup import ATTR_COMPONENT, async_setup_component from homeassistant.util import dt as dt_util from tests.common import assert_lists_same, async_fire_time_changed, load_fixture @@ -165,6 +170,230 @@ async def test_alerts( } +@pytest.mark.parametrize( + ( + "ha_version", + "supervisor_info", + "initial_components", + "late_components", + "initial_alerts", + "late_alerts", + ), + ( + ( + "2022.7.0", + {"version": "2022.11.0"}, + ["aladdin_connect", "darksky"], + [ + "hassio", + "hikvision", + "hikvisioncam", + "hive", + "homematicip_cloud", + "logi_circle", + "neato", + "nest", + "senseme", + "sochain", + ], + [ + ("aladdin_connect", "aladdin_connect"), + ("dark_sky", "darksky"), + ], + [ + ("aladdin_connect", "aladdin_connect"), + ("dark_sky", "darksky"), + ("hassio", "hassio"), + ("hikvision", "hikvision"), + ("hikvision", "hikvisioncam"), + ("hive_us", "hive"), + ("homematicip_cloud", "homematicip_cloud"), + ("logi_circle", "logi_circle"), + ("neato", "neato"), + ("nest", "nest"), + ("senseme", "senseme"), + ("sochain", "sochain"), + ], + ), + ( + "2022.8.0", + {"version": "2022.11.1"}, + ["aladdin_connect", "darksky"], + [ + "hassio", + "hikvision", + "hikvisioncam", + "hive", + "homematicip_cloud", + "logi_circle", + "neato", + "nest", + "senseme", + "sochain", + ], + [ + ("dark_sky", "darksky"), + ], + [ + ("dark_sky", "darksky"), + ("hikvision", "hikvision"), + ("hikvision", "hikvisioncam"), + ("hive_us", "hive"), + ("homematicip_cloud", "homematicip_cloud"), + ("logi_circle", "logi_circle"), + ("neato", "neato"), + ("nest", "nest"), + ("senseme", "senseme"), + ("sochain", "sochain"), + ], + ), + ( + "2021.10.0", + None, + ["aladdin_connect", "darksky"], + [ + "hikvision", + "hikvisioncam", + "hive", + "homematicip_cloud", + "logi_circle", + "neato", + "nest", + "senseme", + "sochain", + ], + [ + ("aladdin_connect", "aladdin_connect"), + ("dark_sky", "darksky"), + ], + [ + ("aladdin_connect", "aladdin_connect"), + ("dark_sky", "darksky"), + ("hikvision", "hikvision"), + ("hikvision", "hikvisioncam"), + ("homematicip_cloud", "homematicip_cloud"), + ("logi_circle", "logi_circle"), + ("neato", "neato"), + ("nest", "nest"), + ("senseme", "senseme"), + ("sochain", "sochain"), + ], + ), + ), +) +async def test_alerts_refreshed_on_component_load( + hass: HomeAssistant, + hass_ws_client, + aioclient_mock: AiohttpClientMocker, + ha_version, + supervisor_info, + initial_components, + late_components, + initial_alerts, + late_alerts, + freezer, +) -> None: + """Test alerts are refreshed when components are loaded.""" + + aioclient_mock.clear_requests() + aioclient_mock.get( + "https://alerts.home-assistant.io/alerts.json", + text=load_fixture("alerts_1.json", "homeassistant_alerts"), + ) + for alert in initial_alerts: + stub_alert(aioclient_mock, alert[0]) + for alert in late_alerts: + stub_alert(aioclient_mock, alert[0]) + + for domain in initial_components: + hass.config.components.add(domain) + + with patch( + "homeassistant.components.homeassistant_alerts.__version__", + ha_version, + ), patch( + "homeassistant.components.homeassistant_alerts.is_hassio", + return_value=supervisor_info is not None, + ), patch( + "homeassistant.components.homeassistant_alerts.get_supervisor_info", + return_value=supervisor_info, + ): + assert await async_setup_component(hass, DOMAIN, {}) + + client = await hass_ws_client(hass) + + await client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await client.receive_json() + assert msg["success"] + assert msg["result"] == { + "issues": [ + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": "homeassistant_alerts", + "ignored": False, + "is_fixable": False, + "issue_id": f"{alert}.markdown_{integration}", + "issue_domain": integration, + "learn_more_url": None, + "severity": "warning", + "translation_key": "alert", + "translation_placeholders": { + "title": f"Title for {alert}", + "description": f"Content for {alert}", + }, + } + for alert, integration in initial_alerts + ] + } + + with patch( + "homeassistant.components.homeassistant_alerts.__version__", + ha_version, + ), patch( + "homeassistant.components.homeassistant_alerts.is_hassio", + return_value=supervisor_info is not None, + ), patch( + "homeassistant.components.homeassistant_alerts.get_supervisor_info", + return_value=supervisor_info, + ): + # Fake component_loaded events and wait for debounce + for domain in late_components: + hass.config.components.add(domain) + hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: domain}) + freezer.tick(COMPONENT_LOADED_COOLDOWN + 1) + await hass.async_block_till_done() + + client = await hass_ws_client(hass) + + await client.send_json({"id": 2, "type": "repairs/list_issues"}) + msg = await client.receive_json() + assert msg["success"] + assert msg["result"] == { + "issues": [ + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": "homeassistant_alerts", + "ignored": False, + "is_fixable": False, + "issue_id": f"{alert}.markdown_{integration}", + "issue_domain": integration, + "learn_more_url": None, + "severity": "warning", + "translation_key": "alert", + "translation_placeholders": { + "title": f"Title for {alert}", + "description": f"Content for {alert}", + }, + } + for alert, integration in late_alerts + ] + } + + @pytest.mark.parametrize( ("ha_version", "fixture", "expected_alerts"), (