diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index d72fdf814e5a..ff9aebc4d392 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -1,5 +1,4 @@ """Support for Meteo-France weather data.""" -# pylint: disable=home-assistant-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern import logging @@ -10,7 +9,7 @@ from requests import RequestException from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from .const import DOMAIN, PLATFORMS +from .const import METEO_FRANCE_DATA, PLATFORMS from .coordinator import ( MeteoFranceAlertUpdateCoordinator, MeteoFranceConfigEntry, @@ -24,7 +23,8 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: MeteoFranceConfigEntry) -> bool: """Set up a Meteo-France account from a config entry.""" - hass.data.setdefault(DOMAIN, {}) + if (departments_with_alert := hass.data.get(METEO_FRANCE_DATA)) is None: + departments_with_alert = hass.data[METEO_FRANCE_DATA] = set() client = MeteoFranceClient() @@ -55,7 +55,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: MeteoFranceConfigEntry) department, ) if department is not None and is_valid_warning_department(department): - if not hass.data[DOMAIN].get(department): + if department not in departments_with_alert: coordinator_alert = MeteoFranceAlertUpdateCoordinator( hass, entry, @@ -66,7 +66,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: MeteoFranceConfigEntry) await coordinator_alert.async_refresh() if coordinator_alert.last_update_success: - hass.data[DOMAIN][department] = True + departments_with_alert.add(department) else: _LOGGER.warning( ( @@ -108,7 +108,7 @@ async def async_unload_entry( """Unload a config entry.""" if entry.runtime_data.alert_coordinator: department = entry.runtime_data.forecast_coordinator.data.position.get("dept") - hass.data[DOMAIN][department] = False + hass.data[METEO_FRANCE_DATA].discard(department) _LOGGER.debug( ( "Weather alert for depatment %s unloaded and released. It can be added" @@ -119,8 +119,8 @@ async def async_unload_entry( unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - if not hass.data[DOMAIN]: - hass.data.pop(DOMAIN) + if not hass.data[METEO_FRANCE_DATA]: + hass.data.pop(METEO_FRANCE_DATA) return unload_ok diff --git a/homeassistant/components/meteo_france/const.py b/homeassistant/components/meteo_france/const.py index d57ddf315098..368cc5b13e3d 100644 --- a/homeassistant/components/meteo_france/const.py +++ b/homeassistant/components/meteo_france/const.py @@ -18,9 +18,15 @@ from homeassistant.components.weather import ( ATTR_CONDITION_WINDY_VARIANT, ) from homeassistant.const import Platform +from homeassistant.util.hass_dict import HassKey DOMAIN = "meteo_france" PLATFORMS = [Platform.SENSOR, Platform.WEATHER] + +# Departments that already have a city providing weather alerts. Only one city +# per department may do so, so this is shared between config entries rather than +# owned by any one of them. +METEO_FRANCE_DATA: HassKey[set[str]] = HassKey(DOMAIN) ATTRIBUTION = "Data provided by Météo-France" MODEL = "Météo-France mobile API" MANUFACTURER = "Météo-France" diff --git a/tests/components/meteo_france/test_init.py b/tests/components/meteo_france/test_init.py new file mode 100644 index 000000000000..041e34aa1f7f --- /dev/null +++ b/tests/components/meteo_france/test_init.py @@ -0,0 +1,83 @@ +"""Test Météo France init.""" + +from collections.abc import Generator +from unittest.mock import patch + +import pytest + +from homeassistant.components.meteo_france.const import ( + CONF_CITY, + DOMAIN, + METEO_FRANCE_DATA, +) +from homeassistant.config_entries import SOURCE_USER, ConfigEntryState +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +@pytest.fixture(autouse=True) +def override_platforms() -> Generator[None]: + """Override PLATFORMS.""" + with patch("homeassistant.components.meteo_france.PLATFORMS", []): + yield + + +def _second_city(hass: HomeAssistant) -> MockConfigEntry: + """Return a second entry for a different city in the same department.""" + entry_data = { + CONF_CITY: "Le Grand-Bornand", + CONF_LATITUDE: 45.94179, + CONF_LONGITUDE: 6.42794, + } + config_entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + unique_id=f"{entry_data[CONF_LATITUDE], entry_data[CONF_LONGITUDE]}", + title=entry_data[CONF_CITY], + data=entry_data, + ) + config_entry.add_to_hass(hass) + return config_entry + + +async def test_only_one_city_per_department_provides_alerts( + hass: HomeAssistant, + config_entry: MockConfigEntry, +) -> None: + """Test the second city in a department does not also provide alerts.""" + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.runtime_data.alert_coordinator is not None + + second_entry = _second_city(hass) + await hass.config_entries.async_setup(second_entry.entry_id) + await hass.async_block_till_done() + + assert second_entry.state is ConfigEntryState.LOADED + assert second_entry.runtime_data.alert_coordinator is None + + +async def test_unload_releases_the_department( + hass: HomeAssistant, + config_entry: MockConfigEntry, +) -> None: + """Test unloading releases the department so another city can claim it.""" + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert hass.data[METEO_FRANCE_DATA] + + assert await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + + # The last entry is gone, so the shared registry is cleaned up entirely. + assert METEO_FRANCE_DATA not in hass.data + + second_entry = _second_city(hass) + await hass.config_entries.async_setup(second_entry.entry_id) + await hass.async_block_till_done() + + assert second_entry.runtime_data.alert_coordinator is not None