From 91aea843fc8da0572eb997c1a814ce43ed3718df Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Wed, 27 Dec 2023 15:56:09 +1000 Subject: [PATCH] Move Trunks from Button to Cover in Tessie (#106448) * Move trunks from buttons to covers * Add tests * Cleanup snapshot * Use Constants * StrEnum to IntEnum --- homeassistant/components/tessie/button.py | 8 -- homeassistant/components/tessie/const.py | 9 +- homeassistant/components/tessie/cover.py | 83 +++++++++++--- homeassistant/components/tessie/strings.json | 8 +- .../tessie/snapshots/test_cover.ambr | 57 +++++++++ tests/components/tessie/test_cover.py | 108 +++++++----------- 6 files changed, 181 insertions(+), 92 deletions(-) create mode 100644 tests/components/tessie/snapshots/test_cover.ambr diff --git a/homeassistant/components/tessie/button.py b/homeassistant/components/tessie/button.py index df918d057a2..817bdb3a87c 100644 --- a/homeassistant/components/tessie/button.py +++ b/homeassistant/components/tessie/button.py @@ -9,8 +9,6 @@ from tessie_api import ( enable_keyless_driving, flash_lights, honk, - open_close_rear_trunk, - open_front_trunk, trigger_homelink, wake, ) @@ -49,12 +47,6 @@ DESCRIPTIONS: tuple[TessieButtonEntityDescription, ...] = ( TessieButtonEntityDescription( key="boombox", func=lambda: boombox, icon="mdi:volume-high" ), - TessieButtonEntityDescription( - key="frunk", func=lambda: open_front_trunk, icon="mdi:car" - ), - TessieButtonEntityDescription( - key="trunk", func=lambda: open_close_rear_trunk, icon="mdi:car-back" - ), ) diff --git a/homeassistant/components/tessie/const.py b/homeassistant/components/tessie/const.py index a6ff7932fa4..2ba4e514579 100644 --- a/homeassistant/components/tessie/const.py +++ b/homeassistant/components/tessie/const.py @@ -1,7 +1,7 @@ """Constants used by Tessie integration.""" from __future__ import annotations -from enum import StrEnum +from enum import IntEnum, StrEnum DOMAIN = "tessie" @@ -46,3 +46,10 @@ class TessieUpdateStatus(StrEnum): INSTALLING = "installing" WIFI_WAIT = "downloading_wifi_wait" SCHEDULED = "scheduled" + + +class TessieCoverStates(IntEnum): + """Tessie Cover states.""" + + CLOSED = 0 + OPEN = 1 diff --git a/homeassistant/components/tessie/cover.py b/homeassistant/components/tessie/cover.py index dddda068d61..6b4393fce1f 100644 --- a/homeassistant/components/tessie/cover.py +++ b/homeassistant/components/tessie/cover.py @@ -6,6 +6,8 @@ from typing import Any from tessie_api import ( close_charge_port, close_windows, + open_close_rear_trunk, + open_front_trunk, open_unlock_charge_port, vent_windows, ) @@ -19,7 +21,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN +from .const import DOMAIN, TessieCoverStates from .coordinator import TessieStateUpdateCoordinator from .entity import TessieEntity @@ -31,10 +33,12 @@ async def async_setup_entry( data = hass.data[DOMAIN][entry.entry_id] async_add_entities( - Entity(vehicle.state_coordinator) - for Entity in ( + klass(vehicle.state_coordinator) + for klass in ( TessieWindowEntity, TessieChargePortEntity, + TessieFrontTrunkEntity, + TessieRearTrunkEntity, ) for vehicle in data ) @@ -54,30 +58,30 @@ class TessieWindowEntity(TessieEntity, CoverEntity): def is_closed(self) -> bool | None: """Return if the cover is closed or not.""" return ( - self.get("vehicle_state_fd_window") == 0 - and self.get("vehicle_state_fp_window") == 0 - and self.get("vehicle_state_rd_window") == 0 - and self.get("vehicle_state_rp_window") == 0 + self.get("vehicle_state_fd_window") == TessieCoverStates.CLOSED + and self.get("vehicle_state_fp_window") == TessieCoverStates.CLOSED + and self.get("vehicle_state_rd_window") == TessieCoverStates.CLOSED + and self.get("vehicle_state_rp_window") == TessieCoverStates.CLOSED ) async def async_open_cover(self, **kwargs: Any) -> None: """Open windows.""" await self.run(vent_windows) self.set( - ("vehicle_state_fd_window", 1), - ("vehicle_state_fp_window", 1), - ("vehicle_state_rd_window", 1), - ("vehicle_state_rp_window", 1), + ("vehicle_state_fd_window", TessieCoverStates.OPEN), + ("vehicle_state_fp_window", TessieCoverStates.OPEN), + ("vehicle_state_rd_window", TessieCoverStates.OPEN), + ("vehicle_state_rp_window", TessieCoverStates.OPEN), ) async def async_close_cover(self, **kwargs: Any) -> None: """Close windows.""" await self.run(close_windows) self.set( - ("vehicle_state_fd_window", 0), - ("vehicle_state_fp_window", 0), - ("vehicle_state_rd_window", 0), - ("vehicle_state_rp_window", 0), + ("vehicle_state_fd_window", TessieCoverStates.CLOSED), + ("vehicle_state_fp_window", TessieCoverStates.CLOSED), + ("vehicle_state_rd_window", TessieCoverStates.CLOSED), + ("vehicle_state_rp_window", TessieCoverStates.CLOSED), ) @@ -105,3 +109,52 @@ class TessieChargePortEntity(TessieEntity, CoverEntity): """Close windows.""" await self.run(close_charge_port) self.set((self.key, False)) + + +class TessieFrontTrunkEntity(TessieEntity, CoverEntity): + """Cover entity for the charge port.""" + + _attr_device_class = CoverDeviceClass.DOOR + _attr_supported_features = CoverEntityFeature.OPEN + + def __init__(self, coordinator: TessieStateUpdateCoordinator) -> None: + """Initialize the sensor.""" + super().__init__(coordinator, "vehicle_state_ft") + + @property + def is_closed(self) -> bool | None: + """Return if the cover is closed or not.""" + return self._value == TessieCoverStates.CLOSED + + async def async_open_cover(self, **kwargs: Any) -> None: + """Open front trunk.""" + await self.run(open_front_trunk) + self.set((self.key, TessieCoverStates.OPEN)) + + +class TessieRearTrunkEntity(TessieEntity, CoverEntity): + """Cover entity for the charge port.""" + + _attr_device_class = CoverDeviceClass.DOOR + _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE + + def __init__(self, coordinator: TessieStateUpdateCoordinator) -> None: + """Initialize the sensor.""" + super().__init__(coordinator, "vehicle_state_rt") + + @property + def is_closed(self) -> bool | None: + """Return if the cover is closed or not.""" + return self._value == TessieCoverStates.CLOSED + + async def async_open_cover(self, **kwargs: Any) -> None: + """Open rear trunk.""" + if self._value == TessieCoverStates.CLOSED: + await self.run(open_close_rear_trunk) + self.set((self.key, TessieCoverStates.OPEN)) + + async def async_close_cover(self, **kwargs: Any) -> None: + """Close rear trunk.""" + if self._value == TessieCoverStates.OPEN: + await self.run(open_close_rear_trunk) + self.set((self.key, TessieCoverStates.CLOSED)) diff --git a/homeassistant/components/tessie/strings.json b/homeassistant/components/tessie/strings.json index 473f67888cd..a72b65591e2 100644 --- a/homeassistant/components/tessie/strings.json +++ b/homeassistant/components/tessie/strings.json @@ -123,7 +123,9 @@ }, "charge_state_charge_port_door_open": { "name": "Charge port door" - } + }, + "vehicle_state_ft": { "name": "Frunk" }, + "vehicle_state_rt": { "name": "Trunk" } }, "select": { "climate_state_seat_heater_left": { @@ -267,9 +269,7 @@ "honk": { "name": "Honk horn" }, "trigger_homelink": { "name": "Homelink" }, "enable_keyless_driving": { "name": "Keyless driving" }, - "boombox": { "name": "Play fart" }, - "frunk": { "name": "Open frunk" }, - "trunk": { "name": "Open/Close trunk" } + "boombox": { "name": "Play fart" } }, "switch": { "charge_state_charge_enable_request": { diff --git a/tests/components/tessie/snapshots/test_cover.ambr b/tests/components/tessie/snapshots/test_cover.ambr new file mode 100644 index 00000000000..ae5e95be68d --- /dev/null +++ b/tests/components/tessie/snapshots/test_cover.ambr @@ -0,0 +1,57 @@ +# serializer version: 1 +# name: test_covers[cover.test_charge_port_door-open_unlock_charge_port-close_charge_port][cover.test_charge_port_door] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'door', + 'friendly_name': 'Test Charge port door', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.test_charge_port_door', + 'last_changed': , + 'last_updated': , + 'state': 'open', + }) +# --- +# name: test_covers[cover.test_frunk-open_front_trunk-False][cover.test_frunk] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'door', + 'friendly_name': 'Test Frunk', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.test_frunk', + 'last_changed': , + 'last_updated': , + 'state': 'closed', + }) +# --- +# name: test_covers[cover.test_trunk-open_close_rear_trunk-open_close_rear_trunk][cover.test_trunk] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'door', + 'friendly_name': 'Test Trunk', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.test_trunk', + 'last_changed': , + 'last_updated': , + 'state': 'closed', + }) +# --- +# name: test_covers[cover.test_vent_windows-vent_windows-close_windows][cover.test_vent_windows] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'window', + 'friendly_name': 'Test Vent windows', + 'supported_features': , + }), + 'context': , + 'entity_id': 'cover.test_vent_windows', + 'last_changed': , + 'last_updated': , + 'state': 'closed', + }) +# --- diff --git a/tests/components/tessie/test_cover.py b/tests/components/tessie/test_cover.py index 426b8ab1696..713108b962a 100644 --- a/tests/components/tessie/test_cover.py +++ b/tests/components/tessie/test_cover.py @@ -2,6 +2,7 @@ from unittest.mock import patch import pytest +from syrupy import SnapshotAssertion from homeassistant.components.cover import ( DOMAIN as COVER_DOMAIN, @@ -17,78 +18,57 @@ from homeassistant.exceptions import HomeAssistantError from .common import ERROR_UNKNOWN, TEST_RESPONSE, TEST_RESPONSE_ERROR, setup_platform -async def test_window(hass: HomeAssistant) -> None: +@pytest.mark.parametrize( + ("entity_id", "openfunc", "closefunc"), + [ + ("cover.test_vent_windows", "vent_windows", "close_windows"), + ("cover.test_charge_port_door", "open_unlock_charge_port", "close_charge_port"), + ("cover.test_frunk", "open_front_trunk", False), + ("cover.test_trunk", "open_close_rear_trunk", "open_close_rear_trunk"), + ], +) +async def test_covers( + hass: HomeAssistant, + entity_id: str, + openfunc: str, + closefunc: str, + snapshot: SnapshotAssertion, +) -> None: """Tests that the window cover entity is correct.""" await setup_platform(hass) - entity_id = "cover.test_vent_windows" - assert hass.states.get(entity_id).state == STATE_CLOSED + assert hass.states.get(entity_id) == snapshot(name=entity_id) # Test open windows - with patch( - "homeassistant.components.tessie.cover.vent_windows", - return_value=TEST_RESPONSE, - ) as mock_set: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: [entity_id]}, - blocking=True, - ) - mock_set.assert_called_once() - assert hass.states.get(entity_id).state == STATE_OPEN + if openfunc: + with patch( + f"homeassistant.components.tessie.cover.{openfunc}", + return_value=TEST_RESPONSE, + ) as mock_open: + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: [entity_id]}, + blocking=True, + ) + mock_open.assert_called_once() + assert hass.states.get(entity_id).state == STATE_OPEN # Test close windows - with patch( - "homeassistant.components.tessie.cover.close_windows", - return_value=TEST_RESPONSE, - ) as mock_set: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: [entity_id]}, - blocking=True, - ) - mock_set.assert_called_once() - assert hass.states.get(entity_id).state == STATE_CLOSED - - -async def test_charge_port(hass: HomeAssistant) -> None: - """Tests that the charge port cover entity is correct.""" - - await setup_platform(hass) - - entity_id = "cover.test_charge_port_door" - assert hass.states.get(entity_id).state == STATE_OPEN - - # Test close charge port - with patch( - "homeassistant.components.tessie.cover.close_charge_port", - return_value=TEST_RESPONSE, - ) as mock_set: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: [entity_id]}, - blocking=True, - ) - mock_set.assert_called_once() - assert hass.states.get(entity_id).state == STATE_CLOSED - - # Test open charge port - with patch( - "homeassistant.components.tessie.cover.open_unlock_charge_port", - return_value=TEST_RESPONSE, - ) as mock_set: - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: [entity_id]}, - blocking=True, - ) - mock_set.assert_called_once() - assert hass.states.get(entity_id).state == STATE_OPEN + if closefunc: + with patch( + f"homeassistant.components.tessie.cover.{closefunc}", + return_value=TEST_RESPONSE, + ) as mock_close: + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: [entity_id]}, + blocking=True, + ) + mock_close.assert_called_once() + assert hass.states.get(entity_id).state == STATE_CLOSED async def test_errors(hass: HomeAssistant) -> None: