"""Stub out homeassistant before any local imports so tests run without HA installed.""" from __future__ import annotations import sys from collections import deque from datetime import timedelta from unittest.mock import MagicMock import pytest # --------------------------------------------------------------------------- # HA module stubs — must happen before coordinator.py is imported # --------------------------------------------------------------------------- class _DataUpdateCoordinator: """Minimal stand-in for DataUpdateCoordinator.""" def __init__(self, hass, logger, *, name, update_interval): self.hass = hass self.name = name self.data: dict = {} class _UpdateFailed(Exception): pass _update_coordinator_stub = MagicMock() _update_coordinator_stub.DataUpdateCoordinator = _DataUpdateCoordinator _update_coordinator_stub.UpdateFailed = _UpdateFailed for _mod in [ "homeassistant", "homeassistant.config_entries", "homeassistant.core", "homeassistant.components", "homeassistant.components.sensor", "homeassistant.components.switch", "homeassistant.const", "homeassistant.helpers", "homeassistant.helpers.device_registry", "homeassistant.helpers.entity_platform", "homeassistant.helpers.event", "homeassistant.helpers.restore_state", "homeassistant.helpers.selector", "voluptuous", ]: sys.modules.setdefault(_mod, MagicMock()) sys.modules["homeassistant.helpers.update_coordinator"] = _update_coordinator_stub # --------------------------------------------------------------------------- # Shared test fixtures # --------------------------------------------------------------------------- from custom_components.easee_solar_charging.coordinator import EaseeSolarCoordinator # noqa: E402 from custom_components.easee_solar_charging.const import ( # noqa: E402 CONF_STOP_GRACE_MINUTES, DEFAULT_STOP_GRACE_MINUTES, ) CHARGER_ID = "ehxt9pqp" PRODUCTION_SENSOR = "sensor.solar_production" HOUSE_SENSOR = "sensor.house_load" EV_SENSOR = "sensor.ev_charging" class FakeCoordinator(EaseeSolarCoordinator): """Coordinator with HA wiring stripped out so logic methods can be unit-tested.""" def __init__(self, options: dict | None = None) -> None: # Skip DataUpdateCoordinator.__init__ — not needed for logic-only tests. self.hass = MagicMock() self.hass.config.time_zone = "UTC" self.charger_id = CHARGER_ID self._production_sensor = PRODUCTION_SENSOR self._house_load_sensor = HOUSE_SENSOR self._ev_charging_sensor = EV_SENSOR self._samples: dict = { PRODUCTION_SENSOR: deque(), HOUSE_SENSOR: deque(), EV_SENSOR: deque(), } self._device_id = None self._last_sent_current = None self._last_sent_at = None self._below_threshold_since = None self.solar_charging_enabled = True self.stop_grace_enabled = True self._entry = MagicMock() self._entry.options = options if options is not None else { CONF_STOP_GRACE_MINUTES: DEFAULT_STOP_GRACE_MINUTES, } @pytest.fixture def coord() -> FakeCoordinator: return FakeCoordinator() def make_state(value: str, attributes: dict | None = None) -> MagicMock: """Return a minimal HA state mock with the given state string and optional attributes.""" s = MagicMock() s.state = value s.attributes = attributes or {} return s