diff --git a/src/devices/hub.py b/src/devices/hub.py index 52da410..f0ff2eb 100644 --- a/src/devices/hub.py +++ b/src/devices/hub.py @@ -4,6 +4,7 @@ from datetime import datetime from typing import Any +from ..helper.compat_aliases import HubCompatMixin from ..helper.const import HIVETOHA, HTTP_OK from ..helper.device_handler_base import BaseDeviceHandler from ..helper.hivedataclasses import Device @@ -11,7 +12,7 @@ _LOGGER = logging.getLogger(__name__) -class HiveHub(BaseDeviceHandler): +class HiveHub(HubCompatMixin, BaseDeviceHandler): """Hive hub. Returns: diff --git a/src/helper/compat_aliases.py b/src/helper/compat_aliases.py index 8cdfc0e..ac2c03b 100644 --- a/src/helper/compat_aliases.py +++ b/src/helper/compat_aliases.py @@ -32,10 +32,66 @@ async def setBoostOff(self, device: Device): # pylint: disable=invalid-name """Backwards-compatible alias for set_boost_off.""" return await self.set_boost_off(device) # type: ignore[attr-defined] + async def setHeatOnDemand(self, device: Device, state: str): # pylint: disable=invalid-name + """Backwards-compatible alias for set_heat_on_demand.""" + return await self.set_heat_on_demand(device, state) # type: ignore[attr-defined] + async def getClimate(self, device: Device): # pylint: disable=invalid-name """Backwards-compatible alias for get_climate.""" return await self.get_climate(device) # type: ignore[attr-defined] + async def getMinTemperature(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_min_temperature.""" + return await self.get_min_temperature(device) # type: ignore[attr-defined] + + async def getMaxTemperature(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_max_temperature.""" + return await self.get_max_temperature(device) # type: ignore[attr-defined] + + async def getCurrentTemperature(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_current_temperature.""" + return await self.get_current_temperature(device) # type: ignore[attr-defined] + + async def getTargetTemperature(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_target_temperature.""" + return await self.get_target_temperature(device) # type: ignore[attr-defined] + + async def getMode(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_mode.""" + return await self.get_mode(device) # type: ignore[attr-defined] + + async def getState(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_state.""" + return await self.get_state(device) # type: ignore[attr-defined] + + async def getCurrentOperation(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_current_operation.""" + return await self.get_current_operation(device) # type: ignore[attr-defined] + + async def getBoostStatus(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_boost_status.""" + return await self.get_boost_status(device) # type: ignore[attr-defined] + + async def getBoostTime(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_boost_time.""" + return await self.get_boost_time(device) # type: ignore[attr-defined] + + async def getHeatOnDemand(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_heat_on_demand.""" + return await self.get_heat_on_demand(device) # type: ignore[attr-defined] + + async def getOperationModes(self): # pylint: disable=invalid-name + """Backwards-compatible alias for get_operation_modes.""" + return await self.get_operation_modes() # type: ignore[attr-defined] + + async def getScheduleNowNextLater(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_schedule_now_next_later.""" + return await self.get_schedule_now_next_later(device) # type: ignore[attr-defined] + + async def minmaxTemperature(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for minmax_temperature.""" + return await self.minmax_temperature(device) # type: ignore[attr-defined] + class LightCompatMixin: """CamelCase aliases for Light public methods.""" @@ -56,6 +112,34 @@ async def getLight(self, device: Device): # pylint: disable=invalid-name """Backwards-compatible alias for get_light.""" return await self.get_light(device) # type: ignore[attr-defined] + async def getState(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_state.""" + return await self.get_state(device) # type: ignore[attr-defined] + + async def getBrightness(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_brightness.""" + return await self.get_brightness(device) # type: ignore[attr-defined] + + async def getMinColorTemp(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_min_color_temp.""" + return await self.get_min_color_temp(device) # type: ignore[attr-defined] + + async def getMaxColorTemp(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_max_color_temp.""" + return await self.get_max_color_temp(device) # type: ignore[attr-defined] + + async def getColorTemp(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_color_temp.""" + return await self.get_color_temp(device) # type: ignore[attr-defined] + + async def getColor(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_color.""" + return await self.get_color(device) # type: ignore[attr-defined] + + async def getColorMode(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_color_mode.""" + return await self.get_color_mode(device) # type: ignore[attr-defined] + class SwitchCompatMixin: """CamelCase aliases for Switch (plug) public methods.""" @@ -72,14 +156,34 @@ async def getSwitch(self, device: Device): # pylint: disable=invalid-name """Backwards-compatible alias for get_switch.""" return await self.get_switch(device) # type: ignore[attr-defined] + async def getState(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_state.""" + return await self.get_state(device) # type: ignore[attr-defined] + + async def getPowerUsage(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_power_usage.""" + return await self.get_power_usage(device) # type: ignore[attr-defined] + + async def getSwitchState(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_switch_state.""" + return await self.get_switch_state(device) # type: ignore[attr-defined] + class WaterHeaterCompatMixin: """CamelCase aliases for WaterHeater (hotwater) public methods.""" - async def get_boost(self, device: Device): # pylint: disable=invalid-name + async def get_boost(self, device: Device): + """Alias for get_boost_status, used by the Hotwater_Boost sensor.""" + return await self.get_boost_status(device) # type: ignore[attr-defined] + + async def getBoost(self, device: Device): # pylint: disable=invalid-name """Backwards-compatible alias for get_boost_status.""" return await self.get_boost_status(device) # type: ignore[attr-defined] + async def getBoostTime(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_boost_time.""" + return await self.get_boost_time(device) # type: ignore[attr-defined] + async def setMode(self, device: Device, new_mode: str): # pylint: disable=invalid-name """Backwards-compatible alias for set_mode.""" return await self.set_mode(device, new_mode) # type: ignore[attr-defined] @@ -96,14 +200,34 @@ async def getWaterHeater(self, device: Device): # pylint: disable=invalid-name """Backwards-compatible alias for get_water_heater.""" return await self.get_water_heater(device) # type: ignore[attr-defined] + async def getMode(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_mode.""" + return await self.get_mode(device) # type: ignore[attr-defined] + + async def getState(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_state.""" + return await self.get_state(device) # type: ignore[attr-defined] -class SensorCompatMixin: # pylint: disable=too-few-public-methods + async def getOperationModes(self): # pylint: disable=invalid-name + """Backwards-compatible alias for get_operation_modes.""" + return await self.get_operation_modes() # type: ignore[attr-defined] + + async def getScheduleNowNextLater(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_schedule_now_next_later.""" + return await self.get_schedule_now_next_later(device) # type: ignore[attr-defined] + + +class SensorCompatMixin: """CamelCase aliases for Sensor public methods.""" async def getSensor(self, device: Device): # pylint: disable=invalid-name """Backwards-compatible alias for get_sensor.""" return await self.get_sensor(device) # type: ignore[attr-defined] + async def getState(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_state.""" + return await self.get_state(device) # type: ignore[attr-defined] + class ActionCompatMixin: """CamelCase aliases for HiveAction public methods.""" @@ -120,6 +244,26 @@ async def setStatusOff(self, device: Device): # pylint: disable=invalid-name """Backwards-compatible alias for set_status_off.""" return await self.set_status_off(device) # type: ignore[attr-defined] + async def getState(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_state.""" + return await self.get_state(device) # type: ignore[attr-defined] + + +class HubCompatMixin: + """CamelCase aliases for HiveHub public methods.""" + + async def getSmokeStatus(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_smoke_status.""" + return await self.get_smoke_status(device) # type: ignore[attr-defined] + + async def getDogBarkStatus(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_dog_bark_status.""" + return await self.get_dog_bark_status(device) # type: ignore[attr-defined] + + async def getGlassBreakStatus(self, device: Device): # pylint: disable=invalid-name + """Backwards-compatible alias for get_glass_break_status.""" + return await self.get_glass_break_status(device) # type: ignore[attr-defined] + class SessionCompatMixin: """CamelCase and legacy aliases for HiveSession public methods.""" diff --git a/tests/unit/test_compat_aliases.py b/tests/unit/test_compat_aliases.py index 8cc255f..16c3440 100644 --- a/tests/unit/test_compat_aliases.py +++ b/tests/unit/test_compat_aliases.py @@ -4,9 +4,11 @@ from unittest.mock import AsyncMock +import pytest from apyhiveapi.helper.compat_aliases import ( ActionCompatMixin, HeatingCompatMixin, + HubCompatMixin, LightCompatMixin, SensorCompatMixin, SessionCompatMixin, @@ -220,6 +222,20 @@ class Stub(WaterHeaterCompatMixin): s.get_boost_status.assert_called_once_with(d) assert result == "OFF" + async def test_camelcase_get_boost_delegates_to_get_boost_status(self): + """getBoost delegates to get_boost_status and returns its result.""" + + class Stub(WaterHeaterCompatMixin): + """Stub with mocked get_boost_status.""" + + get_boost_status = AsyncMock(return_value="ON") + + s = Stub() + d = _make_device() + result = await s.getBoost(d) + s.get_boost_status.assert_called_once_with(d) + assert result == "ON" + async def test_set_mode_delegates(self): """setMode delegates to set_mode.""" @@ -457,3 +473,76 @@ class Stub(ActionCompatMixin): result = await s.setStatusOff(d) s.set_status_off.assert_called_once_with(d) assert result is True + + +# --------------------------------------------------------------------------- +# Getter aliases called by Home Assistant core's hive platforms +# --------------------------------------------------------------------------- + +_DEVICE = object() + +_GETTER_ALIASES = [ + (HeatingCompatMixin, "getMinTemperature", "get_min_temperature", (_DEVICE,)), + (HeatingCompatMixin, "getMaxTemperature", "get_max_temperature", (_DEVICE,)), + ( + HeatingCompatMixin, + "getCurrentTemperature", + "get_current_temperature", + (_DEVICE,), + ), + (HeatingCompatMixin, "getTargetTemperature", "get_target_temperature", (_DEVICE,)), + (HeatingCompatMixin, "getMode", "get_mode", (_DEVICE,)), + (HeatingCompatMixin, "getState", "get_state", (_DEVICE,)), + (HeatingCompatMixin, "getCurrentOperation", "get_current_operation", (_DEVICE,)), + (HeatingCompatMixin, "getBoostStatus", "get_boost_status", (_DEVICE,)), + (HeatingCompatMixin, "getBoostTime", "get_boost_time", (_DEVICE,)), + (HeatingCompatMixin, "getHeatOnDemand", "get_heat_on_demand", (_DEVICE,)), + (HeatingCompatMixin, "setHeatOnDemand", "set_heat_on_demand", (_DEVICE, "ENABLED")), + (HeatingCompatMixin, "getOperationModes", "get_operation_modes", ()), + ( + HeatingCompatMixin, + "getScheduleNowNextLater", + "get_schedule_now_next_later", + (_DEVICE,), + ), + (HeatingCompatMixin, "minmaxTemperature", "minmax_temperature", (_DEVICE,)), + (LightCompatMixin, "getState", "get_state", (_DEVICE,)), + (LightCompatMixin, "getBrightness", "get_brightness", (_DEVICE,)), + (LightCompatMixin, "getMinColorTemp", "get_min_color_temp", (_DEVICE,)), + (LightCompatMixin, "getMaxColorTemp", "get_max_color_temp", (_DEVICE,)), + (LightCompatMixin, "getColorTemp", "get_color_temp", (_DEVICE,)), + (LightCompatMixin, "getColor", "get_color", (_DEVICE,)), + (LightCompatMixin, "getColorMode", "get_color_mode", (_DEVICE,)), + (SwitchCompatMixin, "getState", "get_state", (_DEVICE,)), + (SwitchCompatMixin, "getPowerUsage", "get_power_usage", (_DEVICE,)), + (SwitchCompatMixin, "getSwitchState", "get_switch_state", (_DEVICE,)), + (WaterHeaterCompatMixin, "getBoostTime", "get_boost_time", (_DEVICE,)), + (WaterHeaterCompatMixin, "getMode", "get_mode", (_DEVICE,)), + (WaterHeaterCompatMixin, "getState", "get_state", (_DEVICE,)), + (WaterHeaterCompatMixin, "getOperationModes", "get_operation_modes", ()), + ( + WaterHeaterCompatMixin, + "getScheduleNowNextLater", + "get_schedule_now_next_later", + (_DEVICE,), + ), + (SensorCompatMixin, "getState", "get_state", (_DEVICE,)), + (ActionCompatMixin, "getState", "get_state", (_DEVICE,)), + (HubCompatMixin, "getSmokeStatus", "get_smoke_status", (_DEVICE,)), + (HubCompatMixin, "getDogBarkStatus", "get_dog_bark_status", (_DEVICE,)), + (HubCompatMixin, "getGlassBreakStatus", "get_glass_break_status", (_DEVICE,)), +] + + +@pytest.mark.parametrize( + ("mixin", "alias", "target", "args"), + _GETTER_ALIASES, + ids=[f"{m.__name__}.{a}" for m, a, _, _ in _GETTER_ALIASES], +) +async def test_getter_alias_delegates(mixin, alias, target, args): + """Each camelCase alias delegates to its snake_case method and returns its result.""" + sentinel = object() + stub = type("Stub", (mixin,), {target: AsyncMock(return_value=sentinel)})() + result = await getattr(stub, alias)(*args) + getattr(stub, target).assert_called_once_with(*args) + assert result is sentinel