From a421c524c1bd6d904006164c0723788a8255472e Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 18 Oct 2021 13:27:44 +0200 Subject: [PATCH] Use pytest fixtures on Renault tests (#57955) Co-authored-by: epenet --- tests/components/renault/__init__.py | 322 +++----------- tests/components/renault/conftest.py | 211 +++++++++ tests/components/renault/const.py | 2 + .../components/renault/test_binary_sensor.py | 141 +++--- tests/components/renault/test_config_flow.py | 418 +++++++++--------- .../components/renault/test_device_tracker.py | 136 +++--- tests/components/renault/test_init.py | 35 +- tests/components/renault/test_select.py | 154 +++---- tests/components/renault/test_sensor.py | 209 ++++----- tests/components/renault/test_services.py | 76 +++- 10 files changed, 789 insertions(+), 915 deletions(-) create mode 100644 tests/components/renault/conftest.py diff --git a/tests/components/renault/__init__.py b/tests/components/renault/__init__.py index e95978222f6..7b3bb9e3d0a 100644 --- a/tests/components/renault/__init__.py +++ b/tests/components/renault/__init__.py @@ -1,16 +1,8 @@ """Tests for the Renault integration.""" from __future__ import annotations -import contextlib from types import MappingProxyType -from typing import Any -from unittest.mock import patch -from renault_api.kamereon import schemas -from renault_api.renault_account import RenaultAccount - -from homeassistant.components.renault.const import DOMAIN -from homeassistant.config_entries import SOURCE_USER from homeassistant.const import ( ATTR_ICON, ATTR_IDENTIFIERS, @@ -18,271 +10,23 @@ from homeassistant.const import ( ATTR_MODEL, ATTR_NAME, ATTR_SW_VERSION, + STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import aiohttp_client from homeassistant.helpers.device_registry import DeviceRegistry +from homeassistant.helpers.entity_registry import EntityRegistry -from .const import ICON_FOR_EMPTY_VALUES, MOCK_CONFIG, MOCK_VEHICLES - -from tests.common import MockConfigEntry, load_fixture - - -def get_mock_config_entry(): - """Create the Renault integration.""" - return MockConfigEntry( - domain=DOMAIN, - source=SOURCE_USER, - data=MOCK_CONFIG, - unique_id="account_id_1", - options={}, - entry_id="123456", - ) - - -def get_fixtures(vehicle_type: str) -> dict[str, Any]: - """Create a vehicle proxy for testing.""" - mock_vehicle = MOCK_VEHICLES.get(vehicle_type, {"endpoints": {}}) - return { - "battery_status": schemas.KamereonVehicleDataResponseSchema.loads( - load_fixture(f"renault/{mock_vehicle['endpoints']['battery_status']}") - if "battery_status" in mock_vehicle["endpoints"] - else load_fixture("renault/no_data.json") - ).get_attributes(schemas.KamereonVehicleBatteryStatusDataSchema), - "charge_mode": schemas.KamereonVehicleDataResponseSchema.loads( - load_fixture(f"renault/{mock_vehicle['endpoints']['charge_mode']}") - if "charge_mode" in mock_vehicle["endpoints"] - else load_fixture("renault/no_data.json") - ).get_attributes(schemas.KamereonVehicleChargeModeDataSchema), - "cockpit": schemas.KamereonVehicleDataResponseSchema.loads( - load_fixture(f"renault/{mock_vehicle['endpoints']['cockpit']}") - if "cockpit" in mock_vehicle["endpoints"] - else load_fixture("renault/no_data.json") - ).get_attributes(schemas.KamereonVehicleCockpitDataSchema), - "hvac_status": schemas.KamereonVehicleDataResponseSchema.loads( - load_fixture(f"renault/{mock_vehicle['endpoints']['hvac_status']}") - if "hvac_status" in mock_vehicle["endpoints"] - else load_fixture("renault/no_data.json") - ).get_attributes(schemas.KamereonVehicleHvacStatusDataSchema), - "location": schemas.KamereonVehicleDataResponseSchema.loads( - load_fixture(f"renault/{mock_vehicle['endpoints']['location']}") - if "location" in mock_vehicle["endpoints"] - else load_fixture("renault/no_data.json") - ).get_attributes(schemas.KamereonVehicleLocationDataSchema), - } +from .const import DYNAMIC_ATTRIBUTES, FIXED_ATTRIBUTES, ICON_FOR_EMPTY_VALUES def get_no_data_icon(expected_entity: MappingProxyType): - """Check attribute for icon for inactive sensors.""" + """Check icon attribute for inactive sensors.""" entity_id = expected_entity["entity_id"] return ICON_FOR_EMPTY_VALUES.get(entity_id, expected_entity.get(ATTR_ICON)) -async def setup_renault_integration_simple(hass: HomeAssistant): - """Create the Renault integration.""" - config_entry = get_mock_config_entry() - config_entry.add_to_hass(hass) - - renault_account = RenaultAccount( - config_entry.unique_id, - websession=aiohttp_client.async_get_clientsession(hass), - ) - - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_account", - return_value=renault_account, - ), patch("renault_api.renault_account.RenaultAccount.get_vehicles"): - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - return config_entry - - -@contextlib.contextmanager -def patch_fixtures( - hass: HomeAssistant, config_entry: MockConfigEntry, vehicle_type: str -): - """Mock fixtures.""" - renault_account = RenaultAccount( - config_entry.unique_id, - websession=aiohttp_client.async_get_clientsession(hass), - ) - mock_vehicle = MOCK_VEHICLES[vehicle_type] - mock_fixtures = get_fixtures(vehicle_type) - - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_account", - return_value=renault_account, - ), patch( - "renault_api.renault_account.RenaultAccount.get_vehicles", - return_value=( - schemas.KamereonVehiclesResponseSchema.loads( - load_fixture(f"renault/vehicle_{vehicle_type}.json") - ) - ), - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.supports_endpoint", - side_effect=mock_vehicle["endpoints_available"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.has_contract_for_endpoint", - return_value=True, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", - return_value=mock_fixtures["battery_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", - return_value=mock_fixtures["charge_mode"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", - return_value=mock_fixtures["cockpit"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", - return_value=mock_fixtures["hvac_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_location", - return_value=mock_fixtures["location"], - ): - yield - - -async def setup_renault_integration_vehicle(hass: HomeAssistant, vehicle_type: str): - """Create the Renault integration.""" - config_entry = get_mock_config_entry() - config_entry.add_to_hass(hass) - - with patch_fixtures(hass, config_entry, vehicle_type): - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - return config_entry - - -@contextlib.contextmanager -def patch_fixtures_with_no_data( - hass: HomeAssistant, config_entry: MockConfigEntry, vehicle_type: str -): - """Mock fixtures.""" - renault_account = RenaultAccount( - config_entry.unique_id, - websession=aiohttp_client.async_get_clientsession(hass), - ) - mock_vehicle = MOCK_VEHICLES[vehicle_type] - mock_fixtures = get_fixtures("") - - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_account", - return_value=renault_account, - ), patch( - "renault_api.renault_account.RenaultAccount.get_vehicles", - return_value=( - schemas.KamereonVehiclesResponseSchema.loads( - load_fixture(f"renault/vehicle_{vehicle_type}.json") - ) - ), - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.supports_endpoint", - side_effect=mock_vehicle["endpoints_available"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.has_contract_for_endpoint", - return_value=True, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", - return_value=mock_fixtures["battery_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", - return_value=mock_fixtures["charge_mode"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", - return_value=mock_fixtures["cockpit"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", - return_value=mock_fixtures["hvac_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_location", - return_value=mock_fixtures["location"], - ): - yield - - -async def setup_renault_integration_vehicle_with_no_data( - hass: HomeAssistant, vehicle_type: str -): - """Create the Renault integration.""" - config_entry = get_mock_config_entry() - config_entry.add_to_hass(hass) - - with patch_fixtures_with_no_data(hass, config_entry, vehicle_type): - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - return config_entry - - -@contextlib.contextmanager -def patch_fixtures_with_side_effect( - hass: HomeAssistant, - config_entry: MockConfigEntry, - vehicle_type: str, - side_effect: Any, -): - """Mock fixtures.""" - renault_account = RenaultAccount( - config_entry.unique_id, - websession=aiohttp_client.async_get_clientsession(hass), - ) - mock_vehicle = MOCK_VEHICLES[vehicle_type] - - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_account", - return_value=renault_account, - ), patch( - "renault_api.renault_account.RenaultAccount.get_vehicles", - return_value=( - schemas.KamereonVehiclesResponseSchema.loads( - load_fixture(f"renault/vehicle_{vehicle_type}.json") - ) - ), - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.supports_endpoint", - side_effect=mock_vehicle["endpoints_available"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.has_contract_for_endpoint", - return_value=True, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_location", - side_effect=side_effect, - ): - yield - - -async def setup_renault_integration_vehicle_with_side_effect( - hass: HomeAssistant, vehicle_type: str, side_effect: Any -): - """Create the Renault integration.""" - config_entry = get_mock_config_entry() - config_entry.add_to_hass(hass) - - with patch_fixtures_with_side_effect(hass, config_entry, vehicle_type, side_effect): - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - return config_entry - - def check_device_registry( - device_registry: DeviceRegistry, expected_device: dict[str, Any] + device_registry: DeviceRegistry, expected_device: MappingProxyType ) -> None: """Ensure that the expected_device is correctly registered.""" assert len(device_registry.devices) == 1 @@ -293,3 +37,59 @@ def check_device_registry( assert registry_entry.name == expected_device[ATTR_NAME] assert registry_entry.model == expected_device[ATTR_MODEL] assert registry_entry.sw_version == expected_device[ATTR_SW_VERSION] + + +def check_entities( + hass: HomeAssistant, + entity_registry: EntityRegistry, + expected_entities: MappingProxyType, +) -> None: + """Ensure that the expected_entities are correct.""" + for expected_entity in expected_entities: + entity_id = expected_entity["entity_id"] + registry_entry = entity_registry.entities.get(entity_id) + assert registry_entry is not None + assert registry_entry.unique_id == expected_entity["unique_id"] + state = hass.states.get(entity_id) + assert state.state == expected_entity["result"] + for attr in FIXED_ATTRIBUTES + DYNAMIC_ATTRIBUTES: + assert state.attributes.get(attr) == expected_entity.get(attr) + + +def check_entities_no_data( + hass: HomeAssistant, + entity_registry: EntityRegistry, + expected_entities: MappingProxyType, + expected_state: str, +) -> None: + """Ensure that the expected_entities are correct.""" + for expected_entity in expected_entities: + entity_id = expected_entity["entity_id"] + registry_entry = entity_registry.entities.get(entity_id) + assert registry_entry is not None + assert registry_entry.unique_id == expected_entity["unique_id"] + state = hass.states.get(entity_id) + assert state.state == expected_state + for attr in FIXED_ATTRIBUTES: + assert state.attributes.get(attr) == expected_entity.get(attr) + # Check dynamic attributes: + assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) + + +def check_entities_unavailable( + hass: HomeAssistant, + entity_registry: EntityRegistry, + expected_entities: MappingProxyType, +) -> None: + """Ensure that the expected_entities are correct.""" + for expected_entity in expected_entities: + entity_id = expected_entity["entity_id"] + registry_entry = entity_registry.entities.get(entity_id) + assert registry_entry is not None + assert registry_entry.unique_id == expected_entity["unique_id"] + state = hass.states.get(entity_id) + assert state.state == STATE_UNAVAILABLE + for attr in FIXED_ATTRIBUTES: + assert state.attributes.get(attr) == expected_entity.get(attr) + # Check dynamic attributes: + assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) diff --git a/tests/components/renault/conftest.py b/tests/components/renault/conftest.py new file mode 100644 index 00000000000..a1f3b42167c --- /dev/null +++ b/tests/components/renault/conftest.py @@ -0,0 +1,211 @@ +"""Provide common Renault fixtures.""" +import contextlib +from types import MappingProxyType +from typing import Any +from unittest.mock import patch + +import pytest +from renault_api.kamereon import exceptions, schemas +from renault_api.renault_account import RenaultAccount + +from homeassistant.components.renault.const import DOMAIN +from homeassistant.config_entries import SOURCE_USER, ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import aiohttp_client + +from .const import MOCK_ACCOUNT_ID, MOCK_CONFIG, MOCK_VEHICLES + +from tests.common import MockConfigEntry, load_fixture + + +@pytest.fixture(name="vehicle_type", params=MOCK_VEHICLES.keys()) +def get_vehicle_type(request: pytest.FixtureRequest) -> str: + """Parametrize vehicle type.""" + return request.param + + +@pytest.fixture(name="config_entry") +def get_config_entry(hass: HomeAssistant) -> ConfigEntry: + """Create and register mock config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data=MOCK_CONFIG, + unique_id=MOCK_ACCOUNT_ID, + options={}, + entry_id="123456", + ) + config_entry.add_to_hass(hass) + return config_entry + + +@pytest.fixture(name="patch_renault_account") +async def patch_renault_account(hass: HomeAssistant) -> RenaultAccount: + """Create a Renault account.""" + renault_account = RenaultAccount( + MOCK_ACCOUNT_ID, + websession=aiohttp_client.async_get_clientsession(hass), + ) + with patch("renault_api.renault_session.RenaultSession.login"), patch( + "renault_api.renault_client.RenaultClient.get_api_account", + return_value=renault_account, + ): + yield renault_account + + +@pytest.fixture(name="patch_get_vehicles") +def patch_get_vehicles(vehicle_type: str): + """Mock fixtures.""" + with patch( + "renault_api.renault_account.RenaultAccount.get_vehicles", + return_value=( + schemas.KamereonVehiclesResponseSchema.loads( + load_fixture(f"renault/vehicle_{vehicle_type}.json") + ) + ), + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.supports_endpoint", + side_effect=MOCK_VEHICLES[vehicle_type]["endpoints_available"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.has_contract_for_endpoint", + return_value=True, + ): + yield + + +def _get_fixtures(vehicle_type: str) -> MappingProxyType: + """Create a vehicle proxy for testing.""" + mock_vehicle = MOCK_VEHICLES.get(vehicle_type, {"endpoints": {}}) + return { + "battery_status": schemas.KamereonVehicleDataResponseSchema.loads( + load_fixture(f"renault/{mock_vehicle['endpoints']['battery_status']}") + if "battery_status" in mock_vehicle["endpoints"] + else load_fixture("renault/no_data.json") + ).get_attributes(schemas.KamereonVehicleBatteryStatusDataSchema), + "charge_mode": schemas.KamereonVehicleDataResponseSchema.loads( + load_fixture(f"renault/{mock_vehicle['endpoints']['charge_mode']}") + if "charge_mode" in mock_vehicle["endpoints"] + else load_fixture("renault/no_data.json") + ).get_attributes(schemas.KamereonVehicleChargeModeDataSchema), + "cockpit": schemas.KamereonVehicleDataResponseSchema.loads( + load_fixture(f"renault/{mock_vehicle['endpoints']['cockpit']}") + if "cockpit" in mock_vehicle["endpoints"] + else load_fixture("renault/no_data.json") + ).get_attributes(schemas.KamereonVehicleCockpitDataSchema), + "hvac_status": schemas.KamereonVehicleDataResponseSchema.loads( + load_fixture(f"renault/{mock_vehicle['endpoints']['hvac_status']}") + if "hvac_status" in mock_vehicle["endpoints"] + else load_fixture("renault/no_data.json") + ).get_attributes(schemas.KamereonVehicleHvacStatusDataSchema), + "location": schemas.KamereonVehicleDataResponseSchema.loads( + load_fixture(f"renault/{mock_vehicle['endpoints']['location']}") + if "location" in mock_vehicle["endpoints"] + else load_fixture("renault/no_data.json") + ).get_attributes(schemas.KamereonVehicleLocationDataSchema), + } + + +@pytest.fixture(name="fixtures_with_data") +def patch_fixtures_with_data(vehicle_type: str): + """Mock fixtures.""" + mock_fixtures = _get_fixtures(vehicle_type) + + with patch( + "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", + return_value=mock_fixtures["battery_status"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", + return_value=mock_fixtures["charge_mode"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", + return_value=mock_fixtures["cockpit"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", + return_value=mock_fixtures["hvac_status"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_location", + return_value=mock_fixtures["location"], + ): + yield + + +@pytest.fixture(name="fixtures_with_no_data") +def patch_fixtures_with_no_data(): + """Mock fixtures.""" + mock_fixtures = _get_fixtures("") + + with patch( + "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", + return_value=mock_fixtures["battery_status"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", + return_value=mock_fixtures["charge_mode"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", + return_value=mock_fixtures["cockpit"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", + return_value=mock_fixtures["hvac_status"], + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_location", + return_value=mock_fixtures["location"], + ): + yield + + +@contextlib.contextmanager +def _patch_fixtures_with_side_effect(side_effect: Any): + """Mock fixtures.""" + with patch( + "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", + side_effect=side_effect, + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", + side_effect=side_effect, + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", + side_effect=side_effect, + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", + side_effect=side_effect, + ), patch( + "renault_api.renault_vehicle.RenaultVehicle.get_location", + side_effect=side_effect, + ): + yield + + +@pytest.fixture(name="fixtures_with_access_denied_exception") +def patch_fixtures_with_access_denied_exception(): + """Mock fixtures.""" + access_denied_exception = exceptions.AccessDeniedException( + "err.func.403", + "Access is denied for this resource", + ) + + with _patch_fixtures_with_side_effect(access_denied_exception): + yield + + +@pytest.fixture(name="fixtures_with_invalid_upstream_exception") +def patch_fixtures_with_invalid_upstream_exception(): + """Mock fixtures.""" + invalid_upstream_exception = exceptions.InvalidUpstreamException( + "err.tech.500", + "Invalid response from the upstream server (The request sent to the GDC is erroneous) ; 502 Bad Gateway", + ) + + with _patch_fixtures_with_side_effect(invalid_upstream_exception): + yield + + +@pytest.fixture(name="fixtures_with_not_supported_exception") +def patch_fixtures_with_not_supported_exception(): + """Mock fixtures.""" + not_supported_exception = exceptions.NotSupportedException( + "err.tech.501", + "This feature is not technically supported by this gateway", + ) + + with _patch_fixtures_with_side_effect(not_supported_exception): + yield diff --git a/tests/components/renault/const.py b/tests/components/renault/const.py index 2bcab8ef47f..2e411500d62 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -61,6 +61,8 @@ ICON_FOR_EMPTY_VALUES = { "sensor.plug_state": "mdi:power-plug-off", } +MOCK_ACCOUNT_ID = "account_id_1" + # Mock config data to be used across multiple tests MOCK_CONFIG = { CONF_USERNAME: "email@test.com", diff --git a/tests/components/renault/test_binary_sensor.py b/tests/components/renault/test_binary_sensor.py index 10b9f768501..440018c01c2 100644 --- a/tests/components/renault/test_binary_sensor.py +++ b/tests/components/renault/test_binary_sensor.py @@ -2,133 +2,102 @@ from unittest.mock import patch import pytest -from renault_api.kamereon import exceptions from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN -from homeassistant.const import ATTR_ICON, STATE_OFF, STATE_UNAVAILABLE +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import STATE_OFF from homeassistant.core import HomeAssistant from . import ( check_device_registry, - get_no_data_icon, - setup_renault_integration_vehicle, - setup_renault_integration_vehicle_with_no_data, - setup_renault_integration_vehicle_with_side_effect, + check_entities, + check_entities_no_data, + check_entities_unavailable, ) -from .const import DYNAMIC_ATTRIBUTES, FIXED_ATTRIBUTES, MOCK_VEHICLES +from .const import MOCK_VEHICLES from tests.common import mock_device_registry, mock_registry +pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_binary_sensors(hass: HomeAssistant, vehicle_type: str): + +@pytest.fixture(autouse=True) +def override_platforms(): + """Override PLATFORMS.""" + with patch("homeassistant.components.renault.PLATFORMS", [BINARY_SENSOR_DOMAIN]): + yield + + +@pytest.mark.usefixtures("fixtures_with_data") +async def test_binary_sensors( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault binary sensors.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - with patch("homeassistant.components.renault.PLATFORMS", [BINARY_SENSOR_DOMAIN]): - await setup_renault_integration_vehicle(hass, vehicle_type) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) expected_entities = mock_vehicle[BINARY_SENSOR_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == expected_entity["result"] - for attr in FIXED_ATTRIBUTES + DYNAMIC_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) + + check_entities(hass, entity_registry, expected_entities) -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_binary_sensor_empty(hass: HomeAssistant, vehicle_type: str): +@pytest.mark.usefixtures("fixtures_with_no_data") +async def test_binary_sensor_empty( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault binary sensors with empty data from Renault.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - with patch("homeassistant.components.renault.PLATFORMS", [BINARY_SENSOR_DOMAIN]): - await setup_renault_integration_vehicle_with_no_data(hass, vehicle_type) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) expected_entities = mock_vehicle[BINARY_SENSOR_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == STATE_OFF - for attr in FIXED_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) + check_entities_no_data(hass, entity_registry, expected_entities, STATE_OFF) -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_binary_sensor_errors(hass: HomeAssistant, vehicle_type: str): +@pytest.mark.usefixtures("fixtures_with_invalid_upstream_exception") +async def test_binary_sensor_errors( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault binary sensors with temporary failure.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - invalid_upstream_exception = exceptions.InvalidUpstreamException( - "err.tech.500", - "Invalid response from the upstream server (The request sent to the GDC is erroneous) ; 502 Bad Gateway", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [BINARY_SENSOR_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, invalid_upstream_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) expected_entities = mock_vehicle[BINARY_SENSOR_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == STATE_UNAVAILABLE - for attr in FIXED_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) + + check_entities_unavailable(hass, entity_registry, expected_entities) -async def test_binary_sensor_access_denied(hass): +@pytest.mark.usefixtures("fixtures_with_access_denied_exception") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_binary_sensor_access_denied( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault binary sensors with access denied failure.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - vehicle_type = "zoe_40" - access_denied_exception = exceptions.AccessDeniedException( - "err.func.403", - "Access is denied for this resource", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [BINARY_SENSOR_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, access_denied_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) @@ -136,23 +105,17 @@ async def test_binary_sensor_access_denied(hass): assert len(entity_registry.entities) == 0 -async def test_binary_sensor_not_supported(hass): +@pytest.mark.usefixtures("fixtures_with_not_supported_exception") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_binary_sensor_not_supported( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault binary sensors with not supported failure.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - vehicle_type = "zoe_40" - not_supported_exception = exceptions.NotSupportedException( - "err.tech.501", - "This feature is not technically supported by this gateway", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [BINARY_SENSOR_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, not_supported_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) diff --git a/tests/components/renault/test_config_flow.py b/tests/components/renault/test_config_flow.py index ebf458541f0..ec5eae468fc 100644 --- a/tests/components/renault/test_config_flow.py +++ b/tests/components/renault/test_config_flow.py @@ -1,6 +1,7 @@ """Test the Renault config flow.""" from unittest.mock import AsyncMock, PropertyMock, patch +import pytest from renault_api.gigya.exceptions import InvalidCredentialsException from renault_api.kamereon import schemas from renault_api.renault_account import RenaultAccount @@ -11,251 +12,238 @@ from homeassistant.components.renault.const import ( CONF_LOCALE, DOMAIN, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client -from . import get_mock_config_entry from .const import MOCK_CONFIG from tests.common import load_fixture -async def test_config_flow_single_account(hass: HomeAssistant): +@pytest.fixture(autouse=True, name="mock_setup_entry") +def override_async_setup_entry() -> AsyncMock: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.renault.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry + + +async def test_config_flow_single_account( + hass: HomeAssistant, mock_setup_entry: AsyncMock +): """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + # Failed credentials with patch( - "homeassistant.components.renault.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {} - - # Failed credentials - with patch( - "renault_api.renault_session.RenaultSession.login", - side_effect=InvalidCredentialsException( - 403042, "invalid loginID or password" - ), - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_LOCALE: "fr_FR", - CONF_USERNAME: "email@test.com", - CONF_PASSWORD: "test", - }, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "invalid_credentials"} - - renault_account = AsyncMock() - type(renault_account).account_id = PropertyMock(return_value="account_id_1") - renault_account.get_vehicles.return_value = ( - schemas.KamereonVehiclesResponseSchema.loads( - load_fixture("renault/vehicle_zoe_40.json") - ) - ) - - # Account list single - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_account.RenaultAccount.account_id", return_value="123" - ), patch( - "renault_api.renault_client.RenaultClient.get_api_accounts", - return_value=[renault_account], - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_LOCALE: "fr_FR", - CONF_USERNAME: "email@test.com", - CONF_PASSWORD: "test", - }, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "account_id_1" - assert result["data"][CONF_USERNAME] == "email@test.com" - assert result["data"][CONF_PASSWORD] == "test" - assert result["data"][CONF_KAMEREON_ACCOUNT_ID] == "account_id_1" - assert result["data"][CONF_LOCALE] == "fr_FR" - - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_config_flow_no_account(hass: HomeAssistant): - """Test we get the form.""" - with patch( - "homeassistant.components.renault.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {} - - # Account list empty - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_accounts", - return_value=[], - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_LOCALE: "fr_FR", - CONF_USERNAME: "email@test.com", - CONF_PASSWORD: "test", - }, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "kamereon_no_account" - - assert len(mock_setup_entry.mock_calls) == 0 - - -async def test_config_flow_multiple_accounts(hass: HomeAssistant): - """Test what happens if multiple Kamereon accounts are available.""" - with patch( - "homeassistant.components.renault.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {} - - renault_account_1 = RenaultAccount( - "account_id_1", - websession=aiohttp_client.async_get_clientsession(hass), - ) - renault_account_2 = RenaultAccount( - "account_id_2", - websession=aiohttp_client.async_get_clientsession(hass), - ) - - # Multiple accounts - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_accounts", - return_value=[renault_account_1, renault_account_2], - ), patch("renault_api.renault_account.RenaultAccount.get_vehicles"): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_LOCALE: "fr_FR", - CONF_USERNAME: "email@test.com", - CONF_PASSWORD: "test", - }, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "kamereon" - - # Account selected + "renault_api.renault_session.RenaultSession.login", + side_effect=InvalidCredentialsException(403042, "invalid loginID or password"), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - user_input={CONF_KAMEREON_ACCOUNT_ID: "account_id_2"}, + user_input={ + CONF_LOCALE: "fr_FR", + CONF_USERNAME: "email@test.com", + CONF_PASSWORD: "test", + }, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "account_id_2" - assert result["data"][CONF_USERNAME] == "email@test.com" - assert result["data"][CONF_PASSWORD] == "test" - assert result["data"][CONF_KAMEREON_ACCOUNT_ID] == "account_id_2" - assert result["data"][CONF_LOCALE] == "fr_FR" + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "invalid_credentials"} + + renault_account = AsyncMock() + type(renault_account).account_id = PropertyMock(return_value="account_id_1") + renault_account.get_vehicles.return_value = ( + schemas.KamereonVehiclesResponseSchema.loads( + load_fixture("renault/vehicle_zoe_40.json") + ) + ) + + # Account list single + with patch("renault_api.renault_session.RenaultSession.login"), patch( + "renault_api.renault_account.RenaultAccount.account_id", return_value="123" + ), patch( + "renault_api.renault_client.RenaultClient.get_api_accounts", + return_value=[renault_account], + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_LOCALE: "fr_FR", + CONF_USERNAME: "email@test.com", + CONF_PASSWORD: "test", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "account_id_1" + assert result["data"][CONF_USERNAME] == "email@test.com" + assert result["data"][CONF_PASSWORD] == "test" + assert result["data"][CONF_KAMEREON_ACCOUNT_ID] == "account_id_1" + assert result["data"][CONF_LOCALE] == "fr_FR" assert len(mock_setup_entry.mock_calls) == 1 -async def test_config_flow_duplicate(hass: HomeAssistant): - """Test abort if unique_id configured.""" - with patch( - "homeassistant.components.renault.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - get_mock_config_entry().add_to_hass(hass) - assert len(hass.config_entries.async_entries(DOMAIN)) == 1 +async def test_config_flow_no_account(hass: HomeAssistant, mock_setup_entry: AsyncMock): + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + # Account list empty + with patch("renault_api.renault_session.RenaultSession.login"), patch( + "renault_api.renault_client.RenaultClient.get_api_accounts", + return_value=[], + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_LOCALE: "fr_FR", + CONF_USERNAME: "email@test.com", + CONF_PASSWORD: "test", + }, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {} - renault_account = RenaultAccount( - "account_id_1", - websession=aiohttp_client.async_get_clientsession(hass), - ) - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_accounts", - return_value=[renault_account], - ), patch("renault_api.renault_account.RenaultAccount.get_vehicles"): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_LOCALE: "fr_FR", - CONF_USERNAME: "email@test.com", - CONF_PASSWORD: "test", - }, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - await hass.async_block_till_done() + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "kamereon_no_account" assert len(mock_setup_entry.mock_calls) == 0 -async def test_reauth(hass): - """Test the start of the config flow.""" - with patch( - "homeassistant.components.renault.async_setup_entry", - return_value=True, - ): - original_entry = get_mock_config_entry() - original_entry.add_to_hass(hass) - assert len(hass.config_entries.async_entries(DOMAIN)) == 1 +async def test_config_flow_multiple_accounts( + hass: HomeAssistant, mock_setup_entry: AsyncMock +): + """Test what happens if multiple Kamereon accounts are available.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": original_entry.entry_id, - "unique_id": original_entry.unique_id, + renault_account_1 = RenaultAccount( + "account_id_1", + websession=aiohttp_client.async_get_clientsession(hass), + ) + renault_account_2 = RenaultAccount( + "account_id_2", + websession=aiohttp_client.async_get_clientsession(hass), + ) + + # Multiple accounts + with patch("renault_api.renault_session.RenaultSession.login"), patch( + "renault_api.renault_client.RenaultClient.get_api_accounts", + return_value=[renault_account_1, renault_account_2], + ), patch("renault_api.renault_account.RenaultAccount.get_vehicles"): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_LOCALE: "fr_FR", + CONF_USERNAME: "email@test.com", + CONF_PASSWORD: "test", }, - data=MOCK_CONFIG, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["description_placeholders"] == {CONF_USERNAME: "email@test.com"} - assert result["errors"] == {} + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "kamereon" - # Failed credentials - with patch( - "renault_api.renault_session.RenaultSession.login", - side_effect=InvalidCredentialsException( - 403042, "invalid loginID or password" - ), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_PASSWORD: "any"}, - ) + # Account selected + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_KAMEREON_ACCOUNT_ID: "account_id_2"}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "account_id_2" + assert result["data"][CONF_USERNAME] == "email@test.com" + assert result["data"][CONF_PASSWORD] == "test" + assert result["data"][CONF_KAMEREON_ACCOUNT_ID] == "account_id_2" + assert result["data"][CONF_LOCALE] == "fr_FR" - assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result2["description_placeholders"] == {CONF_USERNAME: "email@test.com"} - assert result2["errors"] == {"base": "invalid_credentials"} + assert len(mock_setup_entry.mock_calls) == 1 - # Valid credentials - with patch("renault_api.renault_session.RenaultSession.login"): - result3 = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_PASSWORD: "any"}, - ) - assert result3["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result3["reason"] == "reauth_successful" +@pytest.mark.usefixtures("config_entry") +async def test_config_flow_duplicate(hass: HomeAssistant, mock_setup_entry: AsyncMock): + """Test abort if unique_id configured.""" + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {} + + renault_account = RenaultAccount( + "account_id_1", + websession=aiohttp_client.async_get_clientsession(hass), + ) + with patch("renault_api.renault_session.RenaultSession.login"), patch( + "renault_api.renault_client.RenaultClient.get_api_accounts", + return_value=[renault_account], + ), patch("renault_api.renault_account.RenaultAccount.get_vehicles"): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_LOCALE: "fr_FR", + CONF_USERNAME: "email@test.com", + CONF_PASSWORD: "test", + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 0 + + +async def test_reauth(hass: HomeAssistant, config_entry: ConfigEntry): + """Test the start of the config flow.""" + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": config_entry.entry_id, + "unique_id": config_entry.unique_id, + }, + data=MOCK_CONFIG, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["description_placeholders"] == {CONF_USERNAME: "email@test.com"} + assert result["errors"] == {} + + # Failed credentials + with patch( + "renault_api.renault_session.RenaultSession.login", + side_effect=InvalidCredentialsException(403042, "invalid loginID or password"), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_PASSWORD: "any"}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["description_placeholders"] == {CONF_USERNAME: "email@test.com"} + assert result2["errors"] == {"base": "invalid_credentials"} + + # Valid credentials + with patch("renault_api.renault_session.RenaultSession.login"): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_PASSWORD: "any"}, + ) + + assert result3["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result3["reason"] == "reauth_successful" diff --git a/tests/components/renault/test_device_tracker.py b/tests/components/renault/test_device_tracker.py index 54f25e4e8cd..a2c8b165b32 100644 --- a/tests/components/renault/test_device_tracker.py +++ b/tests/components/renault/test_device_tracker.py @@ -2,133 +2,106 @@ from unittest.mock import patch import pytest -from renault_api.kamereon import exceptions from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN -from homeassistant.const import ATTR_ICON, STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import STATE_UNKNOWN from homeassistant.core import HomeAssistant from . import ( check_device_registry, - get_no_data_icon, - setup_renault_integration_vehicle, - setup_renault_integration_vehicle_with_no_data, - setup_renault_integration_vehicle_with_side_effect, + check_entities, + check_entities_no_data, + check_entities_unavailable, ) -from .const import DYNAMIC_ATTRIBUTES, FIXED_ATTRIBUTES, MOCK_VEHICLES +from .const import MOCK_VEHICLES from tests.common import mock_device_registry, mock_registry +pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_device_trackers(hass: HomeAssistant, vehicle_type: str): + +@pytest.fixture(autouse=True) +def override_platforms(): + """Override PLATFORMS.""" + with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]): + yield + + +@pytest.mark.usefixtures("fixtures_with_data") +async def test_device_trackers( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault device trackers.""" entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]): - await setup_renault_integration_vehicle(hass, vehicle_type) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) expected_entities = mock_vehicle[DEVICE_TRACKER_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == expected_entity["result"] - for attr in FIXED_ATTRIBUTES + DYNAMIC_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) + + check_entities(hass, entity_registry, expected_entities) -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_device_tracker_empty(hass: HomeAssistant, vehicle_type: str): +@pytest.mark.usefixtures("fixtures_with_no_data") +async def test_device_tracker_empty( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault device trackers with empty data from Renault.""" entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]): - await setup_renault_integration_vehicle_with_no_data(hass, vehicle_type) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) expected_entities = mock_vehicle[DEVICE_TRACKER_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == STATE_UNKNOWN - for attr in FIXED_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) + check_entities_no_data(hass, entity_registry, expected_entities, STATE_UNKNOWN) -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_device_tracker_errors(hass: HomeAssistant, vehicle_type: str): +@pytest.mark.usefixtures("fixtures_with_invalid_upstream_exception") +async def test_device_tracker_errors( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault device trackers with temporary failure.""" entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - invalid_upstream_exception = exceptions.InvalidUpstreamException( - "err.tech.500", - "Invalid response from the upstream server (The request sent to the GDC is erroneous) ; 502 Bad Gateway", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, invalid_upstream_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) expected_entities = mock_vehicle[DEVICE_TRACKER_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == STATE_UNAVAILABLE - for attr in FIXED_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) + + check_entities_unavailable(hass, entity_registry, expected_entities) -async def test_device_tracker_access_denied(hass: HomeAssistant): +@pytest.mark.usefixtures("fixtures_with_access_denied_exception") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_device_tracker_access_denied( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault device trackers with access denied failure.""" entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - vehicle_type = "zoe_40" - access_denied_exception = exceptions.AccessDeniedException( - "err.func.403", - "Access is denied for this resource", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, access_denied_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) @@ -136,23 +109,18 @@ async def test_device_tracker_access_denied(hass: HomeAssistant): assert len(entity_registry.entities) == 0 -async def test_device_tracker_not_supported(hass: HomeAssistant): +@pytest.mark.usefixtures("fixtures_with_not_supported_exception") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_device_tracker_not_supported( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault device trackers with not supported failure.""" entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - vehicle_type = "zoe_40" - not_supported_exception = exceptions.NotSupportedException( - "err.tech.501", - "This feature is not technically supported by this gateway", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [DEVICE_TRACKER_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, not_supported_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) diff --git a/tests/components/renault/test_init.py b/tests/components/renault/test_init.py index 3446bb1f9fa..58da09ccd0b 100644 --- a/tests/components/renault/test_init.py +++ b/tests/components/renault/test_init.py @@ -2,19 +2,32 @@ from unittest.mock import patch import aiohttp +import pytest from renault_api.gigya.exceptions import InvalidCredentialsException from homeassistant.components.renault.const import DOMAIN -from homeassistant.config_entries import ConfigEntryState +from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.core import HomeAssistant -from . import get_mock_config_entry, setup_renault_integration_simple - -async def test_setup_unload_entry(hass: HomeAssistant): - """Test entry setup and unload.""" +@pytest.fixture(autouse=True) +def override_platforms(): + """Override PLATFORMS.""" with patch("homeassistant.components.renault.PLATFORMS", []): - config_entry = await setup_renault_integration_simple(hass) + yield + + +@pytest.fixture(autouse=True, name="vehicle_type", params=["zoe_40"]) +def override_vehicle_type(request) -> str: + """Parametrize vehicle type.""" + return request.param + + +@pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") +async def test_setup_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): + """Test entry setup and unload.""" + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert config_entry.state is ConfigEntryState.LOADED @@ -27,12 +40,9 @@ async def test_setup_unload_entry(hass: HomeAssistant): assert config_entry.entry_id not in hass.data[DOMAIN] -async def test_setup_entry_bad_password(hass: HomeAssistant): +async def test_setup_entry_bad_password(hass: HomeAssistant, config_entry: ConfigEntry): """Test entry setup and unload.""" # Create a mock entry so we don't have to go through config flow - config_entry = get_mock_config_entry() - config_entry.add_to_hass(hass) - with patch( "renault_api.renault_session.RenaultSession.login", side_effect=InvalidCredentialsException(403042, "invalid loginID or password"), @@ -45,11 +55,8 @@ async def test_setup_entry_bad_password(hass: HomeAssistant): assert not hass.data.get(DOMAIN) -async def test_setup_entry_exception(hass: HomeAssistant): +async def test_setup_entry_exception(hass: HomeAssistant, config_entry: ConfigEntry): """Test ConfigEntryNotReady when API raises an exception during entry setup.""" - config_entry = get_mock_config_entry() - config_entry.add_to_hass(hass) - # In this case we are testing the condition where async_setup_entry raises # ConfigEntryNotReady. with patch( diff --git a/tests/components/renault/test_select.py b/tests/components/renault/test_select.py index 5090658464d..b7adaa0c637 100644 --- a/tests/components/renault/test_select.py +++ b/tests/components/renault/test_select.py @@ -2,139 +2,104 @@ from unittest.mock import patch import pytest -from renault_api.kamereon import exceptions, schemas +from renault_api.kamereon import schemas from homeassistant.components.select import DOMAIN as SELECT_DOMAIN from homeassistant.components.select.const import ATTR_OPTION, SERVICE_SELECT_OPTION -from homeassistant.const import ( - ATTR_ENTITY_ID, - ATTR_ICON, - STATE_UNAVAILABLE, - STATE_UNKNOWN, -) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN from homeassistant.core import HomeAssistant from . import ( check_device_registry, - get_no_data_icon, - setup_renault_integration_vehicle, - setup_renault_integration_vehicle_with_no_data, - setup_renault_integration_vehicle_with_side_effect, + check_entities, + check_entities_no_data, + check_entities_unavailable, ) -from .const import DYNAMIC_ATTRIBUTES, FIXED_ATTRIBUTES, MOCK_VEHICLES +from .const import MOCK_VEHICLES from tests.common import load_fixture, mock_device_registry, mock_registry +pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_selects(hass: HomeAssistant, vehicle_type: str): + +@pytest.fixture(autouse=True) +def override_platforms(): + """Override PLATFORMS.""" + with patch("homeassistant.components.renault.PLATFORMS", [SELECT_DOMAIN]): + yield + + +@pytest.mark.usefixtures("fixtures_with_data") +async def test_selects( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault selects.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - with patch("homeassistant.components.renault.PLATFORMS", [SELECT_DOMAIN]): - await setup_renault_integration_vehicle(hass, vehicle_type) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) expected_entities = mock_vehicle[SELECT_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == expected_entity["result"] - for attr in FIXED_ATTRIBUTES + DYNAMIC_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) + + check_entities(hass, entity_registry, expected_entities) -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_select_empty(hass: HomeAssistant, vehicle_type: str): +@pytest.mark.usefixtures("fixtures_with_no_data") +async def test_select_empty( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault selects with empty data from Renault.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - with patch("homeassistant.components.renault.PLATFORMS", [SELECT_DOMAIN]): - await setup_renault_integration_vehicle_with_no_data(hass, vehicle_type) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) expected_entities = mock_vehicle[SELECT_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == STATE_UNKNOWN - for attr in FIXED_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) + check_entities_no_data(hass, entity_registry, expected_entities, STATE_UNKNOWN) -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_select_errors(hass: HomeAssistant, vehicle_type: str): +@pytest.mark.usefixtures("fixtures_with_invalid_upstream_exception") +async def test_select_errors( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault selects with temporary failure.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - invalid_upstream_exception = exceptions.InvalidUpstreamException( - "err.tech.500", - "Invalid response from the upstream server (The request sent to the GDC is erroneous) ; 502 Bad Gateway", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [SELECT_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, invalid_upstream_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) expected_entities = mock_vehicle[SELECT_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == STATE_UNAVAILABLE - for attr in FIXED_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) + + check_entities_unavailable(hass, entity_registry, expected_entities) -async def test_select_access_denied(hass: HomeAssistant): +@pytest.mark.usefixtures("fixtures_with_access_denied_exception") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_select_access_denied( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault selects with access denied failure.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - vehicle_type = "zoe_40" - access_denied_exception = exceptions.AccessDeniedException( - "err.func.403", - "Access is denied for this resource", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [SELECT_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, access_denied_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) @@ -142,23 +107,17 @@ async def test_select_access_denied(hass: HomeAssistant): assert len(entity_registry.entities) == 0 -async def test_select_not_supported(hass: HomeAssistant): +@pytest.mark.usefixtures("fixtures_with_not_supported_exception") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_select_not_supported( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault selects with access denied failure.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - vehicle_type = "zoe_40" - not_supported_exception = exceptions.NotSupportedException( - "err.tech.501", - "This feature is not technically supported by this gateway", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [SELECT_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, not_supported_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) @@ -166,9 +125,12 @@ async def test_select_not_supported(hass: HomeAssistant): assert len(entity_registry.entities) == 0 -async def test_select_charge_mode(hass: HomeAssistant): +@pytest.mark.usefixtures("fixtures_with_data") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_select_charge_mode(hass: HomeAssistant, config_entry: ConfigEntry): """Test that service invokes renault_api with correct data.""" - await setup_renault_integration_vehicle(hass, "zoe_40") + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() data = { ATTR_ENTITY_ID: "select.charge_mode", diff --git a/tests/components/renault/test_sensor.py b/tests/components/renault/test_sensor.py index 79d53d896d3..9e83a131aa8 100644 --- a/tests/components/renault/test_sensor.py +++ b/tests/components/renault/test_sensor.py @@ -1,38 +1,58 @@ """Tests for Renault sensors.""" +from types import MappingProxyType from unittest.mock import patch import pytest -from renault_api.kamereon import exceptions from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.const import ATTR_ICON, STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import STATE_UNKNOWN from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_registry import EntityRegistry from . import ( check_device_registry, - get_no_data_icon, - patch_fixtures, - patch_fixtures_with_no_data, - patch_fixtures_with_side_effect, - setup_renault_integration_vehicle, - setup_renault_integration_vehicle_with_no_data, - setup_renault_integration_vehicle_with_side_effect, + check_entities, + check_entities_no_data, + check_entities_unavailable, ) -from .const import DYNAMIC_ATTRIBUTES, FIXED_ATTRIBUTES, MOCK_VEHICLES +from .const import MOCK_VEHICLES from tests.common import mock_device_registry, mock_registry +pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_sensors(hass: HomeAssistant, vehicle_type: str): + +@pytest.fixture(autouse=True) +def override_platforms(): + """Override PLATFORMS.""" + with patch("homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN]): + yield + + +def _check_and_enable_disabled_entities( + entity_registry: EntityRegistry, expected_entities: MappingProxyType +) -> None: + """Ensure that the expected_entities are correctly disabled.""" + for expected_entity in expected_entities: + if expected_entity.get("default_disabled"): + entity_id = expected_entity["entity_id"] + registry_entry = entity_registry.entities.get(entity_id) + assert registry_entry.disabled + assert registry_entry.disabled_by == "integration" + entity_registry.async_update_entity(entity_id, **{"disabled_by": None}) + + +@pytest.mark.usefixtures("fixtures_with_data") +async def test_sensors( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault sensors.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - with patch("homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN]): - config_entry = await setup_renault_integration_vehicle(hass, vehicle_type) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) @@ -40,43 +60,23 @@ async def test_sensors(hass: HomeAssistant, vehicle_type: str): expected_entities = mock_vehicle[SENSOR_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - # Ensure all entities are enabled - for expected_entity in expected_entities: - if expected_entity.get("default_disabled"): - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry.disabled - assert registry_entry.disabled_by == "integration" - entity_registry.async_update_entity(entity_id, **{"disabled_by": None}) - with patch( - "homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN] - ), patch_fixtures(hass, config_entry, vehicle_type): - await hass.config_entries.async_reload(config_entry.entry_id) - await hass.async_block_till_done() + _check_and_enable_disabled_entities(entity_registry, expected_entities) + await hass.config_entries.async_reload(config_entry.entry_id) + await hass.async_block_till_done() - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == expected_entity["result"] - for attr in FIXED_ATTRIBUTES + DYNAMIC_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) + check_entities(hass, entity_registry, expected_entities) -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_sensor_empty(hass: HomeAssistant, vehicle_type: str): +@pytest.mark.usefixtures("fixtures_with_no_data") +async def test_sensor_empty( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault sensors with empty data from Renault.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - with patch("homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN]): - config_entry = await setup_renault_integration_vehicle_with_no_data( - hass, vehicle_type - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) @@ -84,50 +84,23 @@ async def test_sensor_empty(hass: HomeAssistant, vehicle_type: str): expected_entities = mock_vehicle[SENSOR_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - # Ensure all entities are enabled - for expected_entity in expected_entities: - if expected_entity.get("default_disabled"): - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry.disabled - assert registry_entry.disabled_by == "integration" - entity_registry.async_update_entity(entity_id, **{"disabled_by": None}) - with patch( - "homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN] - ), patch_fixtures_with_no_data(hass, config_entry, vehicle_type): - await hass.config_entries.async_reload(config_entry.entry_id) - await hass.async_block_till_done() + _check_and_enable_disabled_entities(entity_registry, expected_entities) + await hass.config_entries.async_reload(config_entry.entry_id) + await hass.async_block_till_done() - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == STATE_UNKNOWN - for attr in FIXED_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) + check_entities_no_data(hass, entity_registry, expected_entities, STATE_UNKNOWN) -@pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) -async def test_sensor_errors(hass: HomeAssistant, vehicle_type: str): +@pytest.mark.usefixtures("fixtures_with_invalid_upstream_exception") +async def test_sensor_errors( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault sensors with temporary failure.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - invalid_upstream_exception = exceptions.InvalidUpstreamException( - "err.tech.500", - "Invalid response from the upstream server (The request sent to the GDC is erroneous) ; 502 Bad Gateway", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN]): - config_entry = await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, invalid_upstream_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) @@ -135,52 +108,24 @@ async def test_sensor_errors(hass: HomeAssistant, vehicle_type: str): expected_entities = mock_vehicle[SENSOR_DOMAIN] assert len(entity_registry.entities) == len(expected_entities) - # Ensure all entities are enabled - for expected_entity in expected_entities: - if expected_entity.get("default_disabled"): - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry.disabled - assert registry_entry.disabled_by == "integration" - entity_registry.async_update_entity(entity_id, **{"disabled_by": None}) - with patch( - "homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN] - ), patch_fixtures_with_side_effect( - hass, config_entry, vehicle_type, invalid_upstream_exception - ): - await hass.config_entries.async_reload(config_entry.entry_id) - await hass.async_block_till_done() + _check_and_enable_disabled_entities(entity_registry, expected_entities) + await hass.config_entries.async_reload(config_entry.entry_id) + await hass.async_block_till_done() - for expected_entity in expected_entities: - entity_id = expected_entity["entity_id"] - registry_entry = entity_registry.entities.get(entity_id) - assert registry_entry is not None - assert registry_entry.unique_id == expected_entity["unique_id"] - state = hass.states.get(entity_id) - assert state.state == STATE_UNAVAILABLE - for attr in FIXED_ATTRIBUTES: - assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) + check_entities_unavailable(hass, entity_registry, expected_entities) -async def test_sensor_access_denied(hass: HomeAssistant): +@pytest.mark.usefixtures("fixtures_with_access_denied_exception") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_sensor_access_denied( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault sensors with access denied failure.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - vehicle_type = "zoe_40" - access_denied_exception = exceptions.AccessDeniedException( - "err.func.403", - "Access is denied for this resource", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, access_denied_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) @@ -188,23 +133,17 @@ async def test_sensor_access_denied(hass: HomeAssistant): assert len(entity_registry.entities) == 0 -async def test_sensor_not_supported(hass: HomeAssistant): +@pytest.mark.usefixtures("fixtures_with_not_supported_exception") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_sensor_not_supported( + hass: HomeAssistant, config_entry: ConfigEntry, vehicle_type: str +): """Test for Renault sensors with access denied failure.""" - entity_registry = mock_registry(hass) device_registry = mock_device_registry(hass) - vehicle_type = "zoe_40" - not_supported_exception = exceptions.NotSupportedException( - "err.tech.501", - "This feature is not technically supported by this gateway", - ) - - with patch("homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( - hass, vehicle_type, not_supported_exception - ) - await hass.async_block_till_done() + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] check_device_registry(device_registry, mock_vehicle["expected_device"]) diff --git a/tests/components/renault/test_services.py b/tests/components/renault/test_services.py index 37c3d71af61..5a02fd814b9 100644 --- a/tests/components/renault/test_services.py +++ b/tests/components/renault/test_services.py @@ -18,6 +18,7 @@ from homeassistant.components.renault.services import ( SERVICE_CHARGE_START, SERVICES, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_IDENTIFIERS, ATTR_MANUFACTURER, @@ -28,11 +29,24 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr -from . import setup_renault_integration_simple, setup_renault_integration_vehicle - from tests.common import load_fixture from tests.components.renault.const import MOCK_VEHICLES +pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") + + +@pytest.fixture(autouse=True) +def override_platforms(): + """Override PLATFORMS.""" + with patch("homeassistant.components.renault.PLATFORMS", []): + yield + + +@pytest.fixture(autouse=True, name="vehicle_type", params=["zoe_40"]) +def override_vehicle_type(request) -> str: + """Parametrize vehicle type.""" + return request.param + def get_device_id(hass: HomeAssistant) -> str: """Get device_id.""" @@ -42,10 +56,10 @@ def get_device_id(hass: HomeAssistant) -> str: return device.id -async def test_service_registration(hass: HomeAssistant): +async def test_service_registration(hass: HomeAssistant, config_entry: ConfigEntry): """Test entry setup and unload.""" - with patch("homeassistant.components.renault.PLATFORMS", []): - config_entry = await setup_renault_integration_simple(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() # Check that all services are registered. for service in SERVICES: @@ -59,9 +73,10 @@ async def test_service_registration(hass: HomeAssistant): assert not hass.services.has_service(DOMAIN, service) -async def test_service_set_ac_cancel(hass: HomeAssistant): +async def test_service_set_ac_cancel(hass: HomeAssistant, config_entry: ConfigEntry): """Test that service invokes renault_api with correct data.""" - await setup_renault_integration_vehicle(hass, "zoe_40") + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() data = { ATTR_VEHICLE: get_device_id(hass), @@ -82,9 +97,12 @@ async def test_service_set_ac_cancel(hass: HomeAssistant): assert mock_action.mock_calls[0][1] == () -async def test_service_set_ac_start_simple(hass: HomeAssistant): +async def test_service_set_ac_start_simple( + hass: HomeAssistant, config_entry: ConfigEntry +): """Test that service invokes renault_api with correct data.""" - await setup_renault_integration_vehicle(hass, "zoe_40") + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() temperature = 13.5 data = { @@ -107,9 +125,12 @@ async def test_service_set_ac_start_simple(hass: HomeAssistant): assert mock_action.mock_calls[0][1] == (temperature, None) -async def test_service_set_ac_start_with_date(hass: HomeAssistant): +async def test_service_set_ac_start_with_date( + hass: HomeAssistant, config_entry: ConfigEntry +): """Test that service invokes renault_api with correct data.""" - await setup_renault_integration_vehicle(hass, "zoe_40") + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() temperature = 13.5 when = datetime(2025, 8, 23, 17, 12, 45) @@ -134,9 +155,12 @@ async def test_service_set_ac_start_with_date(hass: HomeAssistant): assert mock_action.mock_calls[0][1] == (temperature, when) -async def test_service_set_charge_schedule(hass: HomeAssistant): +async def test_service_set_charge_schedule( + hass: HomeAssistant, config_entry: ConfigEntry +): """Test that service invokes renault_api with correct data.""" - await setup_renault_integration_vehicle(hass, "zoe_40") + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() schedules = {"id": 2} data = { @@ -165,9 +189,12 @@ async def test_service_set_charge_schedule(hass: HomeAssistant): assert mock_action.mock_calls[0][1] == (mock_call_data,) -async def test_service_set_charge_schedule_multi(hass: HomeAssistant): +async def test_service_set_charge_schedule_multi( + hass: HomeAssistant, config_entry: ConfigEntry +): """Test that service invokes renault_api with correct data.""" - await setup_renault_integration_vehicle(hass, "zoe_40") + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() schedules = [ { @@ -209,9 +236,10 @@ async def test_service_set_charge_schedule_multi(hass: HomeAssistant): assert mock_action.mock_calls[0][1] == (mock_call_data,) -async def test_service_set_charge_start(hass: HomeAssistant): +async def test_service_set_charge_start(hass: HomeAssistant, config_entry: ConfigEntry): """Test that service invokes renault_api with correct data.""" - await setup_renault_integration_vehicle(hass, "zoe_40") + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() data = { ATTR_VEHICLE: get_device_id(hass), @@ -232,9 +260,12 @@ async def test_service_set_charge_start(hass: HomeAssistant): assert mock_action.mock_calls[0][1] == () -async def test_service_invalid_device_id(hass: HomeAssistant): +async def test_service_invalid_device_id( + hass: HomeAssistant, config_entry: ConfigEntry +): """Test that service fails with ValueError if device_id not found in registry.""" - await setup_renault_integration_vehicle(hass, "zoe_40") + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() data = {ATTR_VEHICLE: "VF1AAAAA555777999"} @@ -244,9 +275,12 @@ async def test_service_invalid_device_id(hass: HomeAssistant): ) -async def test_service_invalid_device_id2(hass: HomeAssistant): +async def test_service_invalid_device_id2( + hass: HomeAssistant, config_entry: ConfigEntry +): """Test that service fails with ValueError if device_id not found in vehicles.""" - config_entry = await setup_renault_integration_vehicle(hass, "zoe_40") + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() extra_vehicle = MOCK_VEHICLES["captur_phev"]["expected_device"]