"""Define test fixtures for Enphase Envoy.""" from collections.abc import AsyncGenerator, Generator from typing import Any from unittest.mock import AsyncMock, Mock, patch import multidict from pyenphase import ( EnvoyACB, EnvoyACBPower, EnvoyBatteryAggregate, EnvoyC6CC, EnvoyCollar, EnvoyData, EnvoyEncharge, EnvoyEnchargeAggregate, EnvoyEnchargePower, EnvoyEnpower, EnvoyInverter, EnvoySystemConsumption, EnvoySystemProduction, EnvoyTokenAuth, ) from pyenphase.const import SupportedFeatures from pyenphase.models.dry_contacts import EnvoyDryContactSettings, EnvoyDryContactStatus from pyenphase.models.home import EnvoyInterfaceInformation from pyenphase.models.meters import EnvoyMeterData from pyenphase.models.tariff import EnvoyStorageSettings, EnvoyTariff import pytest from homeassistant.components.enphase_envoy import DOMAIN from homeassistant.components.enphase_envoy.const import CONF_MANUAL_TOKEN from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME, ) from homeassistant.core import HomeAssistant from . import envoy_token from tests.common import MockConfigEntry, load_json_object_fixture @pytest.fixture def mock_setup_entry() -> Generator[AsyncMock]: """Override async_setup_entry.""" with patch( "homeassistant.components.enphase_envoy.async_setup_entry", return_value=True, ) as mock_setup_entry: yield mock_setup_entry @pytest.fixture(name="config_entry") def config_entry_fixture( hass: HomeAssistant, config: dict[str, Any], request: pytest.FixtureRequest, ) -> MockConfigEntry: """Define a config entry fixture.""" token_mode = "none" token_life = 365 if hasattr(request, "param"): if isinstance(request.param, list): token_mode = request.param[0] if len(request.param) > 1: token_life = request.param[1] else: token_mode = request.param if token_mode == "none": data = config elif token_mode == "auto": # config contains token from automatic retrieval data = { CONF_HOST: "1.1.1.1", CONF_NAME: "Envoy 1234", CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password", CONF_TOKEN: envoy_token(token_life), CONF_MANUAL_TOKEN: False, } elif token_mode == "manual": # config contains token from manual entry data = { CONF_HOST: "1.1.1.1", CONF_NAME: "Envoy 1234", CONF_USERNAME: "", CONF_PASSWORD: "", CONF_TOKEN: envoy_token(token_life), CONF_MANUAL_TOKEN: True, } return MockConfigEntry( domain=DOMAIN, entry_id="45a36e55aaddb2007c5f6602e0c38e72", title="Envoy 1234", unique_id="1234", data=data, ) @pytest.fixture(name="config") def config_fixture() -> dict[str, str]: """Define a config entry data fixture.""" return { CONF_HOST: "1.1.1.1", CONF_NAME: "Envoy 1234", CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password", } @pytest.fixture async def mock_envoy( request: pytest.FixtureRequest, ) -> AsyncGenerator[AsyncMock]: """Define a mocked Envoy fixture.""" new_token = envoy_token() with ( patch( "homeassistant.components.enphase_envoy.config_flow.Envoy", autospec=True, ) as mock_client, patch( "homeassistant.components.enphase_envoy.Envoy", new=mock_client, ), patch( "pyenphase.auth.EnvoyTokenAuth._obtain_token", return_value=new_token, ), ): mock_envoy = mock_client.return_value # Add the fixtures specified token = envoy_token(200) mock_envoy.auth = EnvoyTokenAuth("127.0.0.1", token=token, envoy_serial="1234") mock_envoy.serial_number = "1234" mock = Mock() mock.status = 200 aiohttp_text = AsyncMock() aiohttp_text.return_value = "Testing request \nreplies." mock.text = aiohttp_text mock.headers = multidict.MultiDict([("Hello", "World")]) mock_envoy.request.return_value = mock # determine fixture file name, default envoy if no request passed fixture_name = "envoy" if hasattr(request, "param"): fixture_name = request.param # Load envoy model from fixture load_envoy_fixture(mock_envoy, fixture_name) mock_envoy.update.return_value = mock_envoy.data yield mock_envoy def load_envoy_fixture(mock_envoy: AsyncMock, fixture_name: str) -> None: """Load envoy model from fixture.""" json_fixture: dict[str, Any] = load_json_object_fixture( f"{fixture_name}.json", DOMAIN ) mock_envoy.firmware = json_fixture["firmware"] mock_envoy.part_number = json_fixture["part_number"] mock_envoy.envoy_model = json_fixture["envoy_model"] mock_envoy.supported_features = SupportedFeatures( json_fixture["supported_features"] ) mock_envoy.phase_mode = json_fixture["phase_mode"] mock_envoy.phase_count = json_fixture["phase_count"] mock_envoy.active_phase_count = json_fixture["active_phase_count"] mock_envoy.ct_meter_count = json_fixture["ct_meter_count"] mock_envoy.consumption_meter_type = json_fixture["consumption_meter_type"] mock_envoy.production_meter_type = json_fixture["production_meter_type"] mock_envoy.storage_meter_type = json_fixture["storage_meter_type"] mock_envoy.data = EnvoyData() _load_json_2_production_data(mock_envoy.data, json_fixture) _load_json_2_meter_data(mock_envoy.data, json_fixture) _load_json_2_inverter_data(mock_envoy.data, json_fixture) _load_json_2_encharge_enpower_data(mock_envoy.data, json_fixture) _load_json_2_acb_inventory_data(mock_envoy.data, json_fixture) _load_json_2_raw_data(mock_envoy.data, json_fixture) if item := json_fixture.get("interface_information"): mock_envoy.interface_settings.return_value = EnvoyInterfaceInformation(**item) else: mock_envoy.interface_settings.return_value = None def _load_json_2_production_data( mocked_data: EnvoyData, json_fixture: dict[str, Any] ) -> None: """Fill envoy production data from fixture.""" if item := json_fixture["data"].get("system_consumption"): mocked_data.system_consumption = EnvoySystemConsumption(**item) if item := json_fixture["data"].get("system_net_consumption"): mocked_data.system_net_consumption = EnvoySystemConsumption(**item) if item := json_fixture["data"].get("system_production"): mocked_data.system_production = EnvoySystemProduction(**item) if item := json_fixture["data"].get("system_consumption_phases"): mocked_data.system_consumption_phases = {} for sub_item, item_data in item.items(): mocked_data.system_consumption_phases[sub_item] = EnvoySystemConsumption( **item_data ) if item := json_fixture["data"].get("system_net_consumption_phases"): mocked_data.system_net_consumption_phases = {} for sub_item, item_data in item.items(): mocked_data.system_net_consumption_phases[sub_item] = ( EnvoySystemConsumption(**item_data) ) if item := json_fixture["data"].get("system_production_phases"): mocked_data.system_production_phases = {} for sub_item, item_data in item.items(): mocked_data.system_production_phases[sub_item] = EnvoySystemProduction( **item_data ) if item := json_fixture["data"].get("acb_power"): mocked_data.acb_power = EnvoyACBPower(**item) def _load_json_2_meter_data( mocked_data: EnvoyData, json_fixture: dict[str, Any] ) -> None: """Fill envoy meter data from fixture.""" if meters := json_fixture["data"].get("ctmeters"): mocked_data.ctmeters = {} [ mocked_data.ctmeters.update({meter: EnvoyMeterData(**meter_data)}) for meter, meter_data in meters.items() ] if meters := json_fixture["data"].get("ctmeters_phases"): mocked_data.ctmeters_phases = {} for meter, meter_data in meters.items(): meter_phase_data: dict[str, EnvoyMeterData] = {} [ meter_phase_data.update({phase: EnvoyMeterData(**phase_data)}) for phase, phase_data in meter_data.items() ] mocked_data.ctmeters_phases.update({meter: meter_phase_data}) def _load_json_2_inverter_data( mocked_data: EnvoyData, json_fixture: dict[str, Any] ) -> None: """Fill envoy inverter data from fixture.""" if item := json_fixture["data"].get("inverters"): mocked_data.inverters = {} for sub_item, item_data in item.items(): mocked_data.inverters[sub_item] = EnvoyInverter(**item_data) def _load_json_2_acb_inventory_data( mocked_data: EnvoyData, json_fixture: dict[str, Any] ) -> None: """Fill envoy per-device ACB inventory data from fixture.""" if item := json_fixture["data"].get("acb_inventory"): mocked_data.acb_inventory = {} for sub_item, item_data in item.items(): mocked_data.acb_inventory[sub_item] = EnvoyACB(**item_data) def _load_json_2_encharge_enpower_data( mocked_data: EnvoyData, json_fixture: dict[str, Any] ) -> None: """Fill envoy encharge/enpower data from fixture.""" if item := json_fixture["data"].get("encharge_inventory"): mocked_data.encharge_inventory = {} for sub_item, item_data in item.items(): mocked_data.encharge_inventory[sub_item] = EnvoyEncharge(**item_data) if item := json_fixture["data"].get("enpower"): mocked_data.enpower = EnvoyEnpower(**item) if item := json_fixture["data"].get("encharge_aggregate"): mocked_data.encharge_aggregate = EnvoyEnchargeAggregate(**item) if item := json_fixture["data"].get("encharge_power"): mocked_data.encharge_power = {} for sub_item, item_data in item.items(): mocked_data.encharge_power[sub_item] = EnvoyEnchargePower(**item_data) if item := json_fixture["data"].get("tariff"): mocked_data.tariff = EnvoyTariff(**item) mocked_data.tariff.storage_settings = EnvoyStorageSettings( **item["storage_settings"] ) if item := json_fixture["data"].get("dry_contact_status"): mocked_data.dry_contact_status = {} for sub_item, item_data in item.items(): mocked_data.dry_contact_status[sub_item] = EnvoyDryContactStatus( **item_data ) if item := json_fixture["data"].get("dry_contact_settings"): mocked_data.dry_contact_settings = {} for sub_item, item_data in item.items(): mocked_data.dry_contact_settings[sub_item] = EnvoyDryContactSettings( **item_data ) if item := json_fixture["data"].get("battery_aggregate"): mocked_data.battery_aggregate = EnvoyBatteryAggregate(**item) if item := json_fixture["data"].get("collar"): mocked_data.collar = EnvoyCollar(**item) if item := json_fixture["data"].get("c6cc"): mocked_data.c6cc = EnvoyC6CC(**item) def _load_json_2_raw_data(mocked_data: EnvoyData, json_fixture: dict[str, Any]) -> None: """Fill envoy raw data from fixture.""" if item := json_fixture["data"].get("raw"): mocked_data.raw = item