diff --git a/tests/test_init.py b/tests/test_init.py index 2016442b..69f61d3a 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -13,7 +13,6 @@ from tests._ha_stubs import ( clear_integration_modules, - force_module, stub_config_entry_class, stub_custom_components_packages, stub_exceptions, @@ -24,11 +23,9 @@ ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -clear_integration_modules() -stub_custom_components_packages(root=ROOT) - -# Provide the additional stubs required by __init__. -homeassistant_mod = stub_homeassistant_package() +integration = None +const = None +_BaseDataUpdateCoordinator = None class _StubConfigEntry: @@ -37,11 +34,6 @@ def __class_getitem__(cls, _item): return cls -stub_config_entry_class(_StubConfigEntry) - -core_mod = types.ModuleType("homeassistant.core") - - class _StubHomeAssistant: # pragma: no cover - structure only pass @@ -50,16 +42,6 @@ class _StubServiceCall: # pragma: no cover - structure only pass -core_mod.HomeAssistant = _StubHomeAssistant -core_mod.ServiceCall = _StubServiceCall -force_module("homeassistant.core", core_mod) - -ha_components_mod = types.ModuleType("homeassistant.components") -force_module("homeassistant.components", ha_components_mod) - -sensor_mod = types.ModuleType("homeassistant.components.sensor") - - class _StubSensorEntity: # pragma: no cover - structure only def __init__(self, *args, **kwargs): self._attr_unique_id = None @@ -83,22 +65,6 @@ class _StubSensorStateClass: # pragma: no cover - structure only MEASUREMENT = "measurement" -sensor_mod.SensorEntity = _StubSensorEntity -sensor_mod.SensorDeviceClass = _StubSensorDeviceClass -sensor_mod.SensorStateClass = _StubSensorStateClass -force_module("homeassistant.components.sensor", sensor_mod) - -const_mod = types.ModuleType("homeassistant.const") -const_mod.ATTR_ATTRIBUTION = "Attribution" -force_module("homeassistant.const", const_mod) - -aiohttp_client_mod = types.ModuleType("homeassistant.helpers.aiohttp_client") -aiohttp_client_mod.async_get_clientsession = lambda _hass: None -force_module("homeassistant.helpers.aiohttp_client", aiohttp_client_mod) - -aiohttp_mod = types.ModuleType("aiohttp") - - class _StubClientError(Exception): pass @@ -112,51 +78,45 @@ def __init__(self, total: float | None = None): self.total = total -aiohttp_mod.ClientError = _StubClientError -aiohttp_mod.ClientSession = _StubClientSession -aiohttp_mod.ClientTimeout = _StubClientTimeout -aiohttp_mod.ContentTypeError = ValueError -force_module("aiohttp", aiohttp_mod) - -cv_mod = types.ModuleType("homeassistant.helpers.config_validation") -cv_mod.config_entry_only_config_schema = lambda _domain: lambda config: config -force_module("homeassistant.helpers.config_validation", cv_mod) - -vol_mod = types.ModuleType("voluptuous") -force_module("voluptuous", vol_mod) -if not hasattr(vol_mod, "Schema"): - vol_mod.Schema = lambda *args, **kwargs: None +class _StubEntityCategory: + DIAGNOSTIC = "diagnostic" -helpers_mod = types.ModuleType("homeassistant.helpers") -force_module("homeassistant.helpers", helpers_mod) -entity_registry_mod = types.ModuleType("homeassistant.helpers.entity_registry") +class _StubConfigEntryNotReady(Exception): + pass -def _stub_async_get(_hass): # pragma: no cover - structure only - class _Registry: - @staticmethod - def async_entries_for_config_entry(_registry, _entry_id): - return [] +class _StubConfigEntryAuthFailed(Exception): + pass - return _Registry() +class _StubUpdateFailed(Exception): + pass -entity_registry_mod.async_get = _stub_async_get -entity_registry_mod.async_entries_for_config_entry = lambda *args, **kwargs: [] -force_module("homeassistant.helpers.entity_registry", entity_registry_mod) -entity_mod = types.ModuleType("homeassistant.helpers.entity") +class _StubCoordinatorEntity: + def __init__(self, coordinator): + self.coordinator = coordinator -class _StubEntityCategory: - DIAGNOSTIC = "diagnostic" +class _StubDataUpdateCoordinator: + def __init__(self, hass, logger, *, name: str, update_interval): + self.hass = hass + self.logger = logger + self.name = name + self.update_interval = update_interval + self.data = {"date": {}, "region": {}} + self.last_updated = None + async def async_config_entry_first_refresh(self): + self.last_updated = "now" + return None -entity_mod.EntityCategory = _StubEntityCategory -force_module("homeassistant.helpers.entity", entity_mod) + async def async_refresh(self): + return None -dt_mod = types.ModuleType("homeassistant.util.dt") + def async_request_refresh(self): # pragma: no cover - scheduling helper + return asyncio.create_task(self.async_refresh()) def _stub_utcnow(): @@ -165,9 +125,6 @@ def _stub_utcnow(): return datetime.now(UTC) -dt_mod.utcnow = _stub_utcnow - - def _stub_parse_http_date(value: str | None): # pragma: no cover - stub only from datetime import UTC, datetime from email.utils import parsedate_to_datetime @@ -189,66 +146,104 @@ def _stub_parse_http_date(value: str | None): # pragma: no cover - stub only return None -dt_mod.parse_http_date = _stub_parse_http_date -force_module("homeassistant.util.dt", dt_mod) - -util_mod = types.ModuleType("homeassistant.util") -util_mod.dt = dt_mod -force_module("homeassistant.util", util_mod) - - -class _StubConfigEntryNotReady(Exception): - pass - - -class _StubConfigEntryAuthFailed(Exception): - pass - - -class _StubUpdateFailed(Exception): - pass - - -class _StubCoordinatorEntity: - def __init__(self, coordinator): - self.coordinator = coordinator - +def _stub_async_get(_hass): # pragma: no cover - structure only + class _Registry: + @staticmethod + def async_entries_for_config_entry(_registry, _entry_id): + return [] -class _StubDataUpdateCoordinator: - def __init__(self, hass, logger, *, name: str, update_interval): - self.hass = hass - self.logger = logger - self.name = name - self.update_interval = update_interval - self.data = {"date": {}, "region": {}} - self.last_updated = None + return _Registry() - async def async_config_entry_first_refresh(self): - self.last_updated = "now" - return None - async def async_refresh(self): - return None +@pytest.fixture +def stub_init_ha_modules(monkeypatch: pytest.MonkeyPatch) -> None: + """Install only the Home Assistant stubs needed by ``__init__`` imports.""" + clear_integration_modules(monkeypatch=monkeypatch) + stub_custom_components_packages(root=ROOT, monkeypatch=monkeypatch) + stub_homeassistant_package(monkeypatch=monkeypatch) + stub_config_entry_class(_StubConfigEntry, monkeypatch=monkeypatch) + core_mod = types.ModuleType("homeassistant.core") + core_mod.HomeAssistant = _StubHomeAssistant + core_mod.ServiceCall = _StubServiceCall + monkeypatch.setitem(sys.modules, "homeassistant.core", core_mod) + + ha_components_mod = types.ModuleType("homeassistant.components") + monkeypatch.setitem(sys.modules, "homeassistant.components", ha_components_mod) + + sensor_mod = types.ModuleType("homeassistant.components.sensor") + sensor_mod.SensorEntity = _StubSensorEntity + sensor_mod.SensorDeviceClass = _StubSensorDeviceClass + sensor_mod.SensorStateClass = _StubSensorStateClass + monkeypatch.setitem(sys.modules, "homeassistant.components.sensor", sensor_mod) + + const_mod = types.ModuleType("homeassistant.const") + const_mod.ATTR_ATTRIBUTION = "Attribution" + monkeypatch.setitem(sys.modules, "homeassistant.const", const_mod) + + aiohttp_client_mod = types.ModuleType("homeassistant.helpers.aiohttp_client") + aiohttp_client_mod.async_get_clientsession = lambda _hass: None + monkeypatch.setitem( + sys.modules, "homeassistant.helpers.aiohttp_client", aiohttp_client_mod + ) + aiohttp_mod = types.ModuleType("aiohttp") + aiohttp_mod.ClientError = _StubClientError + aiohttp_mod.ClientSession = _StubClientSession + aiohttp_mod.ClientTimeout = _StubClientTimeout + aiohttp_mod.ContentTypeError = ValueError + monkeypatch.setitem(sys.modules, "aiohttp", aiohttp_mod) + + cv_mod = types.ModuleType("homeassistant.helpers.config_validation") + cv_mod.config_entry_only_config_schema = lambda _domain: lambda config: config + monkeypatch.setitem(sys.modules, "homeassistant.helpers.config_validation", cv_mod) + + vol_mod = types.ModuleType("voluptuous") + monkeypatch.setitem(sys.modules, "voluptuous", vol_mod) + vol_mod.Schema = lambda *args, **kwargs: None - def async_request_refresh(self): # pragma: no cover - scheduling helper - return asyncio.create_task(self.async_refresh()) + helpers_mod = types.ModuleType("homeassistant.helpers") + monkeypatch.setitem(sys.modules, "homeassistant.helpers", helpers_mod) + entity_registry_mod = types.ModuleType("homeassistant.helpers.entity_registry") + entity_registry_mod.async_get = _stub_async_get + entity_registry_mod.async_entries_for_config_entry = lambda *args, **kwargs: [] + monkeypatch.setitem( + sys.modules, "homeassistant.helpers.entity_registry", entity_registry_mod + ) + entity_mod = types.ModuleType("homeassistant.helpers.entity") + entity_mod.EntityCategory = _StubEntityCategory + monkeypatch.setitem(sys.modules, "homeassistant.helpers.entity", entity_mod) + dt_mod = types.ModuleType("homeassistant.util.dt") + dt_mod.utcnow = _stub_utcnow + dt_mod.parse_http_date = _stub_parse_http_date + monkeypatch.setitem(sys.modules, "homeassistant.util.dt", dt_mod) + + util_mod = types.ModuleType("homeassistant.util") + util_mod.dt = dt_mod + monkeypatch.setitem(sys.modules, "homeassistant.util", util_mod) + stub_exceptions( + ConfigEntryNotReady=_StubConfigEntryNotReady, + ConfigEntryAuthFailed=_StubConfigEntryAuthFailed, + monkeypatch=monkeypatch, + ) + stub_update_coordinator_module( + update_failed=_StubUpdateFailed, + data_update_coordinator=_StubDataUpdateCoordinator, + coordinator_entity=_StubCoordinatorEntity, + monkeypatch=monkeypatch, + ) -stub_exceptions( - ConfigEntryNotReady=_StubConfigEntryNotReady, - ConfigEntryAuthFailed=_StubConfigEntryAuthFailed, -) -stub_update_coordinator_module( - update_failed=_StubUpdateFailed, - data_update_coordinator=_StubDataUpdateCoordinator, - coordinator_entity=_StubCoordinatorEntity, -) -_BaseDataUpdateCoordinator = _StubDataUpdateCoordinator -integration = importlib.import_module( - "custom_components.pollenlevels.__init__" -) # noqa: E402 -const = importlib.import_module("custom_components.pollenlevels.const") # noqa: E402 +@pytest.fixture(autouse=True) +def integration_modules(monkeypatch: pytest.MonkeyPatch, stub_init_ha_modules: None): + """Import integration modules only after stubs are installed.""" + global integration, const, _BaseDataUpdateCoordinator + # Remove the package stub installed by stub_custom_components_packages so + # importing custom_components.pollenlevels executes the real __init__.py. + clear_integration_modules(monkeypatch=monkeypatch) + const = importlib.import_module("custom_components.pollenlevels.const") + integration = importlib.import_module("custom_components.pollenlevels") + _BaseDataUpdateCoordinator = _StubDataUpdateCoordinator + return integration, const class _FakeConfigEntries: diff --git a/tests/test_options_flow.py b/tests/test_options_flow.py index 196b0c77..a4df21a7 100644 --- a/tests/test_options_flow.py +++ b/tests/test_options_flow.py @@ -7,23 +7,24 @@ import pytest -from custom_components.pollenlevels.const import ( - CONF_API_KEY, - CONF_CREATE_FORECAST_SENSORS, - CONF_FORECAST_DAYS, - CONF_LANGUAGE_CODE, - CONF_LATITUDE, - CONF_LONGITUDE, - CONF_UPDATE_INTERVAL, - DEFAULT_FORECAST_DAYS, - DEFAULT_UPDATE_INTERVAL, - FORECAST_SENSORS_CHOICES, - MAX_FORECAST_DAYS, - MAX_UPDATE_INTERVAL_HOURS, - MIN_FORECAST_DAYS, -) +# Importing test_config_flow first installs the Home Assistant stubs needed for +# importing integration modules during collection. from tests import test_config_flow as base +CONF_API_KEY = base.cf.CONF_API_KEY +CONF_CREATE_FORECAST_SENSORS = base.cf.CONF_CREATE_FORECAST_SENSORS +CONF_FORECAST_DAYS = base.cf.CONF_FORECAST_DAYS +CONF_LANGUAGE_CODE = base.cf.CONF_LANGUAGE_CODE +CONF_LATITUDE = base.cf.CONF_LATITUDE +CONF_LONGITUDE = base.cf.CONF_LONGITUDE +CONF_UPDATE_INTERVAL = base.cf.CONF_UPDATE_INTERVAL +DEFAULT_FORECAST_DAYS = base.cf.DEFAULT_FORECAST_DAYS +DEFAULT_UPDATE_INTERVAL = base.cf.DEFAULT_UPDATE_INTERVAL +FORECAST_SENSORS_CHOICES = base.cf.FORECAST_SENSORS_CHOICES +MAX_FORECAST_DAYS = base.cf.MAX_FORECAST_DAYS +MAX_UPDATE_INTERVAL_HOURS = base.cf.MAX_UPDATE_INTERVAL_HOURS +MIN_FORECAST_DAYS = base.cf.MIN_FORECAST_DAYS + PollenLevelsOptionsFlow = base.cf.PollenLevelsOptionsFlow _StubConfigEntry = base._StubConfigEntry