diff --git a/homeassistant/components/renault/renault_entities.py b/homeassistant/components/renault/renault_entities.py index e0aae72298bd..2ea823c25c2f 100644 --- a/homeassistant/components/renault/renault_entities.py +++ b/homeassistant/components/renault/renault_entities.py @@ -1,14 +1,12 @@ """Base classes for Renault entities.""" from __future__ import annotations -from collections.abc import Mapping from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Optional, cast +from typing import Optional, cast from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.util.dt import as_utc, parse_datetime from .renault_coordinator import T from .renault_vehicle import RenaultVehicleProxy @@ -26,9 +24,6 @@ class RenaultEntityDescription(EntityDescription, RenaultRequiredKeysMixin): """Class describing Renault entities.""" -ATTR_LAST_UPDATE = "last_update" - - class RenaultDataEntity(CoordinatorEntity[Optional[T]], Entity): """Implementation of a Renault entity with a data coordinator.""" @@ -51,23 +46,3 @@ class RenaultDataEntity(CoordinatorEntity[Optional[T]], Entity): if self.coordinator.data is None: return None return cast(StateType, getattr(self.coordinator.data, key)) - - @property - def extra_state_attributes(self) -> Mapping[str, Any] | None: - """Return the state attributes of this entity.""" - last_update: str | None = None - if self.entity_description.coordinator == "battery": - last_update = cast(str, self._get_data_attr("timestamp")) - elif self.entity_description.coordinator == "location": - last_update = cast(str, self._get_data_attr("lastUpdateTime")) - if last_update: - return {ATTR_LAST_UPDATE: _convert_to_utc_string(last_update)} - return None - - -def _convert_to_utc_string(value: str) -> str: - """Convert date to UTC iso format.""" - original_dt = parse_datetime(value) - if TYPE_CHECKING: - assert original_dt is not None - return as_utc(original_dt).isoformat() diff --git a/homeassistant/components/renault/sensor.py b/homeassistant/components/renault/sensor.py index bcdb01a05f30..e8e26e06d6c9 100644 --- a/homeassistant/components/renault/sensor.py +++ b/homeassistant/components/renault/sensor.py @@ -3,13 +3,14 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -from typing import cast +from typing import TYPE_CHECKING, cast from renault_api.kamereon.enums import ChargeState, PlugState from renault_api.kamereon.models import ( KamereonVehicleBatteryStatusData, KamereonVehicleCockpitData, KamereonVehicleHvacStatusData, + KamereonVehicleLocationData, ) from homeassistant.components.sensor import ( @@ -25,6 +26,7 @@ from homeassistant.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, ELECTRIC_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, LENGTH_KILOMETERS, @@ -37,6 +39,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType +from homeassistant.util.dt import as_utc, parse_datetime from .const import DEVICE_CLASS_CHARGE_STATE, DEVICE_CLASS_PLUG_STATE, DOMAIN from .renault_coordinator import T @@ -148,6 +151,14 @@ def _get_rounded_value(entity: RenaultSensor[T]) -> float: return round(cast(float, entity.data)) +def _get_utc_value(entity: RenaultSensor[T]) -> str: + """Return the UTC value of this entity.""" + original_dt = parse_datetime(cast(str, entity.data)) + if TYPE_CHECKING: + assert original_dt is not None + return as_utc(original_dt).isoformat() + + SENSOR_TYPES: tuple[RenaultSensorEntityDescription, ...] = ( RenaultSensorEntityDescription( key="battery_level", @@ -242,6 +253,16 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription, ...] = ( native_unit_of_measurement=TEMP_CELSIUS, state_class=STATE_CLASS_MEASUREMENT, ), + RenaultSensorEntityDescription( + key="battery_last_activity", + coordinator="battery", + device_class=DEVICE_CLASS_TIMESTAMP, + data_key="timestamp", + entity_class=RenaultSensor[KamereonVehicleBatteryStatusData], + entity_registry_enabled_default=False, + name="Battery Last Activity", + value_lambda=_get_utc_value, + ), RenaultSensorEntityDescription( key="mileage", coordinator="cockpit", @@ -287,4 +308,14 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription, ...] = ( native_unit_of_measurement=TEMP_CELSIUS, state_class=STATE_CLASS_MEASUREMENT, ), + RenaultSensorEntityDescription( + key="location_last_activity", + coordinator="location", + device_class=DEVICE_CLASS_TIMESTAMP, + data_key="lastUpdateTime", + entity_class=RenaultSensor[KamereonVehicleLocationData], + entity_registry_enabled_default=False, + name="Location Last Activity", + value_lambda=_get_utc_value, + ), ) diff --git a/tests/components/renault/__init__.py b/tests/components/renault/__init__.py index bbca3a741394..e95978222f6b 100644 --- a/tests/components/renault/__init__.py +++ b/tests/components/renault/__init__.py @@ -1,6 +1,7 @@ """Tests for the Renault integration.""" from __future__ import annotations +import contextlib from types import MappingProxyType from typing import Any from unittest.mock import patch @@ -97,11 +98,11 @@ async def setup_renault_integration_simple(hass: HomeAssistant): return config_entry -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) - +@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), @@ -141,19 +142,26 @@ async def setup_renault_integration_vehicle(hass: HomeAssistant, vehicle_type: s "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 -async def setup_renault_integration_vehicle_with_no_data( - hass: HomeAssistant, vehicle_type: str +@contextlib.contextmanager +def patch_fixtures_with_no_data( + hass: HomeAssistant, config_entry: MockConfigEntry, vehicle_type: str ): - """Create the Renault integration.""" - config_entry = get_mock_config_entry() - config_entry.add_to_hass(hass) - + """Mock fixtures.""" renault_account = RenaultAccount( config_entry.unique_id, websession=aiohttp_client.async_get_clientsession(hass), @@ -193,19 +201,31 @@ async def setup_renault_integration_vehicle_with_no_data( "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 -async def setup_renault_integration_vehicle_with_side_effect( - hass: HomeAssistant, vehicle_type: str, side_effect: Any +@contextlib.contextmanager +def patch_fixtures_with_side_effect( + hass: HomeAssistant, + config_entry: MockConfigEntry, + vehicle_type: str, + side_effect: Any, ): - """Create the Renault integration.""" - config_entry = get_mock_config_entry() - config_entry.add_to_hass(hass) - + """Mock fixtures.""" renault_account = RenaultAccount( config_entry.unique_id, websession=aiohttp_client.async_get_clientsession(hass), @@ -244,6 +264,17 @@ async def setup_renault_integration_vehicle_with_side_effect( "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() diff --git a/tests/components/renault/const.py b/tests/components/renault/const.py index f9fb765dab30..2bcab8ef47fb 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -13,7 +13,6 @@ from homeassistant.components.renault.const import ( DEVICE_CLASS_PLUG_STATE, DOMAIN, ) -from homeassistant.components.renault.renault_entities import ATTR_LAST_UPDATE from homeassistant.components.select import DOMAIN as SELECT_DOMAIN from homeassistant.components.select.const import ATTR_OPTIONS from homeassistant.components.sensor import ( @@ -33,6 +32,7 @@ from homeassistant.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, ELECTRIC_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, LENGTH_KILOMETERS, @@ -53,10 +53,7 @@ FIXED_ATTRIBUTES = ( ATTR_STATE_CLASS, ATTR_UNIT_OF_MEASUREMENT, ) -DYNAMIC_ATTRIBUTES = ( - ATTR_ICON, - ATTR_LAST_UPDATE, -) +DYNAMIC_ATTRIBUTES = (ATTR_ICON,) ICON_FOR_EMPTY_VALUES = { "select.charge_mode": "mdi:calendar-remove", @@ -100,14 +97,12 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_plugged_in", "result": STATE_ON, ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", }, { "entity_id": "binary_sensor.charging", "unique_id": "vf1aaaaa555777999_charging", "result": STATE_ON, ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY_CHARGING, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", }, ], DEVICE_TRACKER_DOMAIN: [], @@ -127,7 +122,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_battery_autonomy", "result": "141", ATTR_ICON: "mdi:ev-station", - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS, }, @@ -136,7 +130,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_battery_available_energy", "result": "31", ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, }, @@ -145,16 +138,21 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_battery_level", "result": "60", ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE, }, + { + "entity_id": "sensor.battery_last_activity", + "unique_id": "vf1aaaaa555777999_battery_last_activity", + "result": "2020-01-12T21:40:16+00:00", + "default_disabled": True, + ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, + }, { "entity_id": "sensor.battery_temperature", "unique_id": "vf1aaaaa555777999_battery_temperature", "result": "20", ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, }, @@ -164,14 +162,12 @@ MOCK_VEHICLES = { "result": "charge_in_progress", ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_STATE, ATTR_ICON: "mdi:flash", - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", }, { "entity_id": "sensor.charging_power", "unique_id": "vf1aaaaa555777999_charging_power", "result": "0.027", ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: POWER_KILO_WATT, }, @@ -180,7 +176,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_charging_remaining_time", "result": "145", ATTR_ICON: "mdi:timer", - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: TIME_MINUTES, }, @@ -206,7 +201,6 @@ MOCK_VEHICLES = { "result": "plugged", ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE, ATTR_ICON: "mdi:power-plug", - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", }, ], }, @@ -237,14 +231,12 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_plugged_in", "result": STATE_OFF, ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG, - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", }, { "entity_id": "binary_sensor.charging", "unique_id": "vf1aaaaa555777999_charging", "result": STATE_OFF, ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY_CHARGING, - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", }, ], DEVICE_TRACKER_DOMAIN: [ @@ -253,7 +245,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_location", "result": STATE_NOT_HOME, ATTR_ICON: "mdi:car", - ATTR_LAST_UPDATE: "2020-02-18T16:58:38+00:00", } ], SELECT_DOMAIN: [ @@ -272,7 +263,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_battery_autonomy", "result": "128", ATTR_ICON: "mdi:ev-station", - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS, }, @@ -281,7 +271,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_battery_available_energy", "result": "0", ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, }, @@ -290,16 +279,21 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_battery_level", "result": "50", ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY, - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE, }, + { + "entity_id": "sensor.battery_last_activity", + "unique_id": "vf1aaaaa555777999_battery_last_activity", + "result": "2020-11-17T08:06:48+00:00", + "default_disabled": True, + ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, + }, { "entity_id": "sensor.battery_temperature", "unique_id": "vf1aaaaa555777999_battery_temperature", "result": STATE_UNKNOWN, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, }, @@ -309,14 +303,12 @@ MOCK_VEHICLES = { "result": "charge_error", ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_STATE, ATTR_ICON: "mdi:flash-off", - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", }, { "entity_id": "sensor.charging_power", "unique_id": "vf1aaaaa555777999_charging_power", "result": STATE_UNKNOWN, ATTR_DEVICE_CLASS: DEVICE_CLASS_CURRENT, - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: ELECTRIC_CURRENT_AMPERE, }, @@ -325,7 +317,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777999_charging_remaining_time", "result": STATE_UNKNOWN, ATTR_ICON: "mdi:timer", - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: TIME_MINUTES, }, @@ -343,7 +334,13 @@ MOCK_VEHICLES = { "result": "unplugged", ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE, ATTR_ICON: "mdi:power-plug-off", - ATTR_LAST_UPDATE: "2020-11-17T08:06:48+00:00", + }, + { + "entity_id": "sensor.location_last_activity", + "unique_id": "vf1aaaaa555777999_location_last_activity", + "result": "2020-02-18T16:58:38+00:00", + "default_disabled": True, + ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, }, ], }, @@ -374,14 +371,12 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777123_plugged_in", "result": STATE_ON, ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", }, { "entity_id": "binary_sensor.charging", "unique_id": "vf1aaaaa555777123_charging", "result": STATE_ON, ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY_CHARGING, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", }, ], DEVICE_TRACKER_DOMAIN: [ @@ -390,7 +385,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777123_location", "result": STATE_NOT_HOME, ATTR_ICON: "mdi:car", - ATTR_LAST_UPDATE: "2020-02-18T16:58:38+00:00", } ], SELECT_DOMAIN: [ @@ -409,7 +403,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777123_battery_autonomy", "result": "141", ATTR_ICON: "mdi:ev-station", - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS, }, @@ -418,7 +411,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777123_battery_available_energy", "result": "31", ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, }, @@ -427,16 +419,21 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777123_battery_level", "result": "60", ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE, }, + { + "entity_id": "sensor.battery_last_activity", + "unique_id": "vf1aaaaa555777123_battery_last_activity", + "result": "2020-01-12T21:40:16+00:00", + "default_disabled": True, + ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, + }, { "entity_id": "sensor.battery_temperature", "unique_id": "vf1aaaaa555777123_battery_temperature", "result": "20", ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, }, @@ -446,14 +443,12 @@ MOCK_VEHICLES = { "result": "charge_in_progress", ATTR_DEVICE_CLASS: DEVICE_CLASS_CHARGE_STATE, ATTR_ICON: "mdi:flash", - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", }, { "entity_id": "sensor.charging_power", "unique_id": "vf1aaaaa555777123_charging_power", "result": "27.0", ATTR_DEVICE_CLASS: DEVICE_CLASS_CURRENT, - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: ELECTRIC_CURRENT_AMPERE, }, @@ -462,7 +457,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777123_charging_remaining_time", "result": "145", ATTR_ICON: "mdi:timer", - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT: TIME_MINUTES, }, @@ -496,7 +490,13 @@ MOCK_VEHICLES = { "result": "plugged", ATTR_DEVICE_CLASS: DEVICE_CLASS_PLUG_STATE, ATTR_ICON: "mdi:power-plug", - ATTR_LAST_UPDATE: "2020-01-12T21:40:16+00:00", + }, + { + "entity_id": "sensor.location_last_activity", + "unique_id": "vf1aaaaa555777123_location_last_activity", + "result": "2020-02-18T16:58:38+00:00", + "default_disabled": True, + ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, }, ], }, @@ -526,7 +526,6 @@ MOCK_VEHICLES = { "unique_id": "vf1aaaaa555777123_location", "result": STATE_NOT_HOME, ATTR_ICON: "mdi:car", - ATTR_LAST_UPDATE: "2020-02-18T16:58:38+00:00", } ], SELECT_DOMAIN: [], @@ -555,6 +554,13 @@ MOCK_VEHICLES = { ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, ATTR_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS, }, + { + "entity_id": "sensor.location_last_activity", + "unique_id": "vf1aaaaa555777123_location_last_activity", + "result": "2020-02-18T16:58:38+00:00", + "default_disabled": True, + ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, + }, ], }, } diff --git a/tests/components/renault/test_binary_sensor.py b/tests/components/renault/test_binary_sensor.py index d0fb6e544adb..10b9f7685018 100644 --- a/tests/components/renault/test_binary_sensor.py +++ b/tests/components/renault/test_binary_sensor.py @@ -5,7 +5,6 @@ import pytest from renault_api.kamereon import exceptions from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN -from homeassistant.components.renault.renault_entities import ATTR_LAST_UPDATE from homeassistant.const import ATTR_ICON, STATE_OFF, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant @@ -75,7 +74,6 @@ async def test_binary_sensor_empty(hass: HomeAssistant, vehicle_type: str): assert state.attributes.get(attr) == expected_entity.get(attr) # Check dynamic attributes: assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) - assert ATTR_LAST_UPDATE not in state.attributes @pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) @@ -112,7 +110,6 @@ async def test_binary_sensor_errors(hass: HomeAssistant, vehicle_type: str): assert state.attributes.get(attr) == expected_entity.get(attr) # Check dynamic attributes: assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) - assert ATTR_LAST_UPDATE not in state.attributes async def test_binary_sensor_access_denied(hass): diff --git a/tests/components/renault/test_device_tracker.py b/tests/components/renault/test_device_tracker.py index 2232af18a225..54f25e4e8cd7 100644 --- a/tests/components/renault/test_device_tracker.py +++ b/tests/components/renault/test_device_tracker.py @@ -5,7 +5,6 @@ import pytest from renault_api.kamereon import exceptions from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN -from homeassistant.components.renault.renault_entities import ATTR_LAST_UPDATE from homeassistant.const import ATTR_ICON, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant @@ -75,7 +74,6 @@ async def test_device_tracker_empty(hass: HomeAssistant, vehicle_type: str): assert state.attributes.get(attr) == expected_entity.get(attr) # Check dynamic attributes: assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) - assert ATTR_LAST_UPDATE not in state.attributes @pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) @@ -112,7 +110,6 @@ async def test_device_tracker_errors(hass: HomeAssistant, vehicle_type: str): assert state.attributes.get(attr) == expected_entity.get(attr) # Check dynamic attributes: assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) - assert ATTR_LAST_UPDATE not in state.attributes async def test_device_tracker_access_denied(hass: HomeAssistant): diff --git a/tests/components/renault/test_select.py b/tests/components/renault/test_select.py index 0bc7c4ddc683..5090658464d2 100644 --- a/tests/components/renault/test_select.py +++ b/tests/components/renault/test_select.py @@ -4,7 +4,6 @@ from unittest.mock import patch import pytest from renault_api.kamereon import exceptions, schemas -from homeassistant.components.renault.renault_entities import ATTR_LAST_UPDATE from homeassistant.components.select import DOMAIN as SELECT_DOMAIN from homeassistant.components.select.const import ATTR_OPTION, SERVICE_SELECT_OPTION from homeassistant.const import ( @@ -81,7 +80,6 @@ async def test_select_empty(hass: HomeAssistant, vehicle_type: str): assert state.attributes.get(attr) == expected_entity.get(attr) # Check dynamic attributes: assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) - assert ATTR_LAST_UPDATE not in state.attributes @pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) @@ -118,7 +116,6 @@ async def test_select_errors(hass: HomeAssistant, vehicle_type: str): assert state.attributes.get(attr) == expected_entity.get(attr) # Check dynamic attributes: assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) - assert ATTR_LAST_UPDATE not in state.attributes async def test_select_access_denied(hass: HomeAssistant): diff --git a/tests/components/renault/test_sensor.py b/tests/components/renault/test_sensor.py index e3c758f088a8..79d53d896d38 100644 --- a/tests/components/renault/test_sensor.py +++ b/tests/components/renault/test_sensor.py @@ -4,7 +4,6 @@ from unittest.mock import patch import pytest from renault_api.kamereon import exceptions -from homeassistant.components.renault.renault_entities import ATTR_LAST_UPDATE from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ATTR_ICON, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant @@ -12,6 +11,9 @@ from homeassistant.core import HomeAssistant 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, @@ -29,7 +31,7 @@ async def test_sensors(hass: HomeAssistant, vehicle_type: str): device_registry = mock_device_registry(hass) with patch("homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN]): - await setup_renault_integration_vehicle(hass, vehicle_type) + config_entry = await setup_renault_integration_vehicle(hass, vehicle_type) await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] @@ -37,6 +39,21 @@ 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() + for expected_entity in expected_entities: entity_id = expected_entity["entity_id"] registry_entry = entity_registry.entities.get(entity_id) @@ -56,7 +73,9 @@ async def test_sensor_empty(hass: HomeAssistant, vehicle_type: str): device_registry = mock_device_registry(hass) with patch("homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN]): - await setup_renault_integration_vehicle_with_no_data(hass, vehicle_type) + config_entry = await setup_renault_integration_vehicle_with_no_data( + hass, vehicle_type + ) await hass.async_block_till_done() mock_vehicle = MOCK_VEHICLES[vehicle_type] @@ -64,6 +83,21 @@ 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() + for expected_entity in expected_entities: entity_id = expected_entity["entity_id"] registry_entry = entity_registry.entities.get(entity_id) @@ -75,7 +109,6 @@ async def test_sensor_empty(hass: HomeAssistant, vehicle_type: str): assert state.attributes.get(attr) == expected_entity.get(attr) # Check dynamic attributes: assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) - assert ATTR_LAST_UPDATE not in state.attributes @pytest.mark.parametrize("vehicle_type", MOCK_VEHICLES.keys()) @@ -91,7 +124,7 @@ async def test_sensor_errors(hass: HomeAssistant, vehicle_type: str): ) with patch("homeassistant.components.renault.PLATFORMS", [SENSOR_DOMAIN]): - await setup_renault_integration_vehicle_with_side_effect( + config_entry = await setup_renault_integration_vehicle_with_side_effect( hass, vehicle_type, invalid_upstream_exception ) await hass.async_block_till_done() @@ -101,6 +134,23 @@ 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() + for expected_entity in expected_entities: entity_id = expected_entity["entity_id"] registry_entry = entity_registry.entities.get(entity_id) @@ -112,7 +162,6 @@ async def test_sensor_errors(hass: HomeAssistant, vehicle_type: str): assert state.attributes.get(attr) == expected_entity.get(attr) # Check dynamic attributes: assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) - assert ATTR_LAST_UPDATE not in state.attributes async def test_sensor_access_denied(hass: HomeAssistant):