From 52ef4a3b750546667a7969ee57f38189e4eb175c Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Mon, 29 May 2023 23:28:06 +0200 Subject: [PATCH] Ensure state update after BMW remote service execution (#93745) Co-authored-by: rikroe --- .../components/bmw_connected_drive/button.py | 3 --- homeassistant/components/bmw_connected_drive/lock.py | 4 ++++ .../components/bmw_connected_drive/number.py | 2 ++ .../components/bmw_connected_drive/select.py | 2 ++ .../components/bmw_connected_drive/switch.py | 4 ++++ tests/components/bmw_connected_drive/conftest.py | 12 +++++++++++- tests/components/bmw_connected_drive/test_number.py | 9 +++++++++ tests/components/bmw_connected_drive/test_select.py | 7 +++++++ tests/components/bmw_connected_drive/test_switch.py | 8 ++++++++ 9 files changed, 47 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/button.py b/homeassistant/components/bmw_connected_drive/button.py index 0ecc07357fe..5285820b32d 100644 --- a/homeassistant/components/bmw_connected_drive/button.py +++ b/homeassistant/components/bmw_connected_drive/button.py @@ -122,7 +122,4 @@ class BMWButton(BMWBaseEntity, ButtonEntity): ) await self.entity_description.account_function(self.coordinator) - # Always update HA states after a button was executed. - # BMW remote services that change the vehicle's state update the local object - # when executing the service, so only the HA state machine needs further updates. self.coordinator.async_update_listeners() diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index ffc6cf6d8b7..d20ccd1fbb4 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -68,6 +68,8 @@ class BMWLock(BMWBaseEntity, LockEntity): self.async_write_ha_state() await self.vehicle.remote_services.trigger_remote_door_lock() + self.coordinator.async_update_listeners() + async def async_unlock(self, **kwargs: Any) -> None: """Unlock the car.""" _LOGGER.debug("%s: unlocking doors", self.vehicle.name) @@ -79,6 +81,8 @@ class BMWLock(BMWBaseEntity, LockEntity): self.async_write_ha_state() await self.vehicle.remote_services.trigger_remote_door_unlock() + self.coordinator.async_update_listeners() + @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" diff --git a/homeassistant/components/bmw_connected_drive/number.py b/homeassistant/components/bmw_connected_drive/number.py index 820257f6163..c8f72b272c1 100644 --- a/homeassistant/components/bmw_connected_drive/number.py +++ b/homeassistant/components/bmw_connected_drive/number.py @@ -116,3 +116,5 @@ class BMWNumber(BMWBaseEntity, NumberEntity): await self.entity_description.remote_service(self.vehicle, value) except MyBMWAPIError as ex: raise HomeAssistantError(ex) from ex + + self.coordinator.async_update_listeners() diff --git a/homeassistant/components/bmw_connected_drive/select.py b/homeassistant/components/bmw_connected_drive/select.py index 52d35b477a2..0b20ed90873 100644 --- a/homeassistant/components/bmw_connected_drive/select.py +++ b/homeassistant/components/bmw_connected_drive/select.py @@ -124,3 +124,5 @@ class BMWSelect(BMWBaseEntity, SelectEntity): option, ) await self.entity_description.remote_service(self.vehicle, option) + + self.coordinator.async_update_listeners() diff --git a/homeassistant/components/bmw_connected_drive/switch.py b/homeassistant/components/bmw_connected_drive/switch.py index af7a42b35b0..0ef7d8801af 100644 --- a/homeassistant/components/bmw_connected_drive/switch.py +++ b/homeassistant/components/bmw_connected_drive/switch.py @@ -101,9 +101,13 @@ class BMWSwitch(BMWBaseEntity, SwitchEntity): except MyBMWAPIError as ex: raise HomeAssistantError(ex) from ex + self.coordinator.async_update_listeners() + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" try: await self.entity_description.remote_service_off(self.vehicle) except MyBMWAPIError as ex: raise HomeAssistantError(ex) from ex + + self.coordinator.async_update_listeners() diff --git a/tests/components/bmw_connected_drive/conftest.py b/tests/components/bmw_connected_drive/conftest.py index 73e8f9a9b92..b65adb5b2c0 100644 --- a/tests/components/bmw_connected_drive/conftest.py +++ b/tests/components/bmw_connected_drive/conftest.py @@ -1,11 +1,15 @@ """Fixtures for BMW tests.""" -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, Mock from bimmer_connected.api.authentication import MyBMWAuthentication from bimmer_connected.vehicle.remote_services import RemoteServices, RemoteServiceStatus import pytest +from homeassistant.components.bmw_connected_drive.coordinator import ( + BMWDataUpdateCoordinator, +) + from . import mock_login, mock_vehicles @@ -20,5 +24,11 @@ async def bmw_fixture(monkeypatch): AsyncMock(return_value=RemoteServiceStatus({"eventStatus": "EXECUTED"})), ) + monkeypatch.setattr( + BMWDataUpdateCoordinator, + "async_update_listeners", + Mock(), + ) + with mock_vehicles(): yield mock_vehicles diff --git a/tests/components/bmw_connected_drive/test_number.py b/tests/components/bmw_connected_drive/test_number.py index b6c16af3e03..d8cd5d47867 100644 --- a/tests/components/bmw_connected_drive/test_number.py +++ b/tests/components/bmw_connected_drive/test_number.py @@ -7,6 +7,9 @@ import pytest import respx from syrupy.assertion import SnapshotAssertion +from homeassistant.components.bmw_connected_drive.coordinator import ( + BMWDataUpdateCoordinator, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -43,6 +46,7 @@ async def test_update_triggers_success( # Setup component assert await setup_mocked_integration(hass) + BMWDataUpdateCoordinator.async_update_listeners.reset_mock() # Test await hass.services.async_call( @@ -53,6 +57,7 @@ async def test_update_triggers_success( target={"entity_id": entity_id}, ) assert RemoteServices.trigger_remote_service.call_count == 1 + assert BMWDataUpdateCoordinator.async_update_listeners.call_count == 1 @pytest.mark.parametrize( @@ -71,6 +76,7 @@ async def test_update_triggers_fail( # Setup component assert await setup_mocked_integration(hass) + BMWDataUpdateCoordinator.async_update_listeners.reset_mock() # Test with pytest.raises(ValueError): @@ -82,6 +88,7 @@ async def test_update_triggers_fail( target={"entity_id": entity_id}, ) assert RemoteServices.trigger_remote_service.call_count == 0 + assert BMWDataUpdateCoordinator.async_update_listeners.call_count == 0 @pytest.mark.parametrize( @@ -103,6 +110,7 @@ async def test_update_triggers_exceptions( # Setup component assert await setup_mocked_integration(hass) + BMWDataUpdateCoordinator.async_update_listeners.reset_mock() # Setup exception monkeypatch.setattr( @@ -121,3 +129,4 @@ async def test_update_triggers_exceptions( target={"entity_id": "number.i4_edrive40_target_soc"}, ) assert RemoteServices.trigger_remote_service.call_count == 1 + assert BMWDataUpdateCoordinator.async_update_listeners.call_count == 0 diff --git a/tests/components/bmw_connected_drive/test_select.py b/tests/components/bmw_connected_drive/test_select.py index bbef62b14ed..b5a13a13b63 100644 --- a/tests/components/bmw_connected_drive/test_select.py +++ b/tests/components/bmw_connected_drive/test_select.py @@ -4,6 +4,9 @@ import pytest import respx from syrupy.assertion import SnapshotAssertion +from homeassistant.components.bmw_connected_drive.coordinator import ( + BMWDataUpdateCoordinator, +) from homeassistant.core import HomeAssistant from . import setup_mocked_integration @@ -41,6 +44,7 @@ async def test_update_triggers_success( # Setup component assert await setup_mocked_integration(hass) + BMWDataUpdateCoordinator.async_update_listeners.reset_mock() # Test await hass.services.async_call( @@ -51,6 +55,7 @@ async def test_update_triggers_success( target={"entity_id": entity_id}, ) assert RemoteServices.trigger_remote_service.call_count == 1 + assert BMWDataUpdateCoordinator.async_update_listeners.call_count == 1 @pytest.mark.parametrize( @@ -69,6 +74,7 @@ async def test_update_triggers_fail( # Setup component assert await setup_mocked_integration(hass) + BMWDataUpdateCoordinator.async_update_listeners.reset_mock() # Test with pytest.raises(ValueError): @@ -80,3 +86,4 @@ async def test_update_triggers_fail( target={"entity_id": entity_id}, ) assert RemoteServices.trigger_remote_service.call_count == 0 + assert BMWDataUpdateCoordinator.async_update_listeners.call_count == 0 diff --git a/tests/components/bmw_connected_drive/test_switch.py b/tests/components/bmw_connected_drive/test_switch.py index fd9871dfecd..06c01e19689 100644 --- a/tests/components/bmw_connected_drive/test_switch.py +++ b/tests/components/bmw_connected_drive/test_switch.py @@ -7,6 +7,9 @@ import pytest import respx from syrupy.assertion import SnapshotAssertion +from homeassistant.components.bmw_connected_drive.coordinator import ( + BMWDataUpdateCoordinator, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -22,6 +25,7 @@ async def test_entity_state_attrs( # Setup component assert await setup_mocked_integration(hass) + BMWDataUpdateCoordinator.async_update_listeners.reset_mock() # Get all switch entities assert hass.states.async_all("switch") == snapshot @@ -44,6 +48,7 @@ async def test_update_triggers_success( # Setup component assert await setup_mocked_integration(hass) + BMWDataUpdateCoordinator.async_update_listeners.reset_mock() # Test await hass.services.async_call( @@ -53,6 +58,7 @@ async def test_update_triggers_success( target={"entity_id": entity_id}, ) assert RemoteServices.trigger_remote_service.call_count == 1 + assert BMWDataUpdateCoordinator.async_update_listeners.call_count == 1 @pytest.mark.parametrize( @@ -74,6 +80,7 @@ async def test_update_triggers_exceptions( # Setup component assert await setup_mocked_integration(hass) + BMWDataUpdateCoordinator.async_update_listeners.reset_mock() # Setup exception monkeypatch.setattr( @@ -98,3 +105,4 @@ async def test_update_triggers_exceptions( target={"entity_id": "switch.i4_edrive40_climate"}, ) assert RemoteServices.trigger_remote_service.call_count == 2 + assert BMWDataUpdateCoordinator.async_update_listeners.call_count == 0