From 3480fb69962e5eefaddf46d3756e9be3476add20 Mon Sep 17 00:00:00 2001 From: Eugene Prystupa Date: Wed, 22 Jul 2020 18:22:25 -0700 Subject: [PATCH] Refactor bond integration to be completely async (#38066) --- homeassistant/components/bond/__init__.py | 6 +- homeassistant/components/bond/config_flow.py | 35 ++++---- homeassistant/components/bond/cover.py | 20 ++--- homeassistant/components/bond/fan.py | 40 +++++---- homeassistant/components/bond/light.py | 34 +++---- homeassistant/components/bond/manifest.json | 2 +- homeassistant/components/bond/switch.py | 18 ++-- homeassistant/components/bond/utils.py | 24 ++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/bond/common.py | 71 +++------------ tests/components/bond/test_config_flow.py | 19 ++-- tests/components/bond/test_cover.py | 39 ++++---- tests/components/bond/test_fan.py | 94 ++++++++++++-------- tests/components/bond/test_light.py | 75 ++++++++++------ tests/components/bond/test_switch.py | 29 +++--- 16 files changed, 251 insertions(+), 259 deletions(-) diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index 013b061c08e..60c78ee4dbe 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -1,7 +1,7 @@ """The Bond integration.""" import asyncio -from bond import Bond +from bond_api import Bond from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST @@ -25,9 +25,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): host = entry.data[CONF_HOST] token = entry.data[CONF_ACCESS_TOKEN] - bond = Bond(bondIp=host, bondToken=token) + bond = Bond(host=host, token=token) hub = BondHub(bond) - await hass.async_add_executor_job(hub.setup) + await hub.setup() hass.data[DOMAIN][entry.entry_id] = hub device_registry = await dr.async_get_registry(hass) diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index b2f009af44f..215ae4af91d 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -1,12 +1,11 @@ """Config flow for Bond integration.""" -from json import JSONDecodeError import logging -from bond import Bond -from requests.exceptions import ConnectionError as RequestConnectionError +from aiohttp import ClientConnectionError, ClientResponseError +from bond_api import Bond import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant import config_entries, exceptions from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST from .const import DOMAIN # pylint:disable=unused-import @@ -18,24 +17,20 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(data): """Validate the user input allows us to connect.""" - def authenticate(bond_hub: Bond) -> bool: - try: - bond_hub.getDeviceIds() - return True - except RequestConnectionError: - raise CannotConnect - except JSONDecodeError: - return False + try: + bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN]) + await bond.devices() + except ClientConnectionError: + raise CannotConnect + except ClientResponseError as error: + if error.status == 401: + raise InvalidAuth + raise - bond = Bond(data[CONF_HOST], data[CONF_ACCESS_TOKEN]) - - if not await hass.async_add_executor_job(authenticate, bond): - raise InvalidAuth - - # Return info that you want to store in the config entry. + # Return info to be stored in the config entry. return {"title": data[CONF_HOST]} @@ -50,7 +45,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors = {} if user_input is not None: try: - info = await validate_input(self.hass, user_input) + info = await validate_input(user_input) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index 809bf3d7da5..523a7f5c4d8 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -1,7 +1,7 @@ """Support for Bond covers.""" from typing import Any, Callable, List, Optional -from bond import DeviceTypes +from bond_api import Action, DeviceType from homeassistant.components.cover import DEVICE_CLASS_SHADE, CoverEntity from homeassistant.config_entries import ConfigEntry @@ -24,7 +24,7 @@ async def async_setup_entry( covers = [ BondCover(hub, device) for device in hub.devices - if device.type == DeviceTypes.MOTORIZED_SHADES + if device.type == DeviceType.MOTORIZED_SHADES ] async_add_entities(covers, True) @@ -44,9 +44,9 @@ class BondCover(BondEntity, CoverEntity): """Get device class.""" return DEVICE_CLASS_SHADE - def update(self): + async def async_update(self): """Fetch assumed state of the cover from the hub using API.""" - state: dict = self._hub.bond.getDeviceState(self._device.device_id) + state: dict = await self._hub.bond.device_state(self._device.device_id) cover_open = state.get("open") self._closed = True if cover_open == 0 else False if cover_open == 1 else None @@ -55,14 +55,14 @@ class BondCover(BondEntity, CoverEntity): """Return if the cover is closed or not.""" return self._closed - def open_cover(self, **kwargs: Any) -> None: + async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" - self._hub.bond.open(self._device.device_id) + await self._hub.bond.action(self._device.device_id, Action.open()) - def close_cover(self, **kwargs: Any) -> None: + async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" - self._hub.bond.close(self._device.device_id) + await self._hub.bond.action(self._device.device_id, Action.close()) - def stop_cover(self, **kwargs): + async def async_stop_cover(self, **kwargs): """Hold cover.""" - self._hub.bond.hold(self._device.device_id) + await self._hub.bond.action(self._device.device_id, Action.hold()) diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 80ae5d7f6ac..82d437fd7b0 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -2,7 +2,7 @@ import math from typing import Any, Callable, List, Optional -from bond import DeviceTypes, Directions +from bond_api import Action, DeviceType, Direction from homeassistant.components.fan import ( DIRECTION_FORWARD, @@ -33,9 +33,7 @@ async def async_setup_entry( hub: BondHub = hass.data[DOMAIN][entry.entry_id] fans = [ - BondFan(hub, device) - for device in hub.devices - if device.type == DeviceTypes.CEILING_FAN + BondFan(hub, device) for device in hub.devices if DeviceType.is_fan(device.type) ] async_add_entities(fans, True) @@ -85,21 +83,21 @@ class BondFan(BondEntity, FanEntity): def current_direction(self) -> Optional[str]: """Return fan rotation direction.""" direction = None - if self._direction == Directions.FORWARD: + if self._direction == Direction.FORWARD: direction = DIRECTION_FORWARD - elif self._direction == Directions.REVERSE: + elif self._direction == Direction.REVERSE: direction = DIRECTION_REVERSE return direction - def update(self): + async def async_update(self): """Fetch assumed state of the fan from the hub using API.""" - state: dict = self._hub.bond.getDeviceState(self._device.device_id) + state: dict = await self._hub.bond.device_state(self._device.device_id) self._power = state.get("power") self._speed = state.get("speed") self._direction = state.get("direction") - def set_speed(self, speed: str) -> None: + async def async_set_speed(self, speed: str) -> None: """Set the desired speed for the fan.""" max_speed = self._device.props.get("max_speed", 3) if speed == SPEED_LOW: @@ -108,21 +106,27 @@ class BondFan(BondEntity, FanEntity): bond_speed = max_speed else: bond_speed = math.ceil(max_speed / 2) - self._hub.bond.setSpeed(self._device.device_id, speed=bond_speed) - def turn_on(self, speed: Optional[str] = None, **kwargs) -> None: + await self._hub.bond.action( + self._device.device_id, Action.set_speed(bond_speed) + ) + + async def async_turn_on(self, speed: Optional[str] = None, **kwargs) -> None: """Turn on the fan.""" if speed is not None: - self.set_speed(speed) - self._hub.bond.turnOn(self._device.device_id) + await self.async_set_speed(speed) + else: + await self._hub.bond.action(self._device.device_id, Action.turn_on()) - def turn_off(self, **kwargs: Any) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the fan off.""" - self._hub.bond.turnOff(self._device.device_id) + await self._hub.bond.action(self._device.device_id, Action.turn_off()) - def set_direction(self, direction: str) -> None: + async def async_set_direction(self, direction: str): """Set fan rotation direction.""" bond_direction = ( - Directions.REVERSE if direction == DIRECTION_REVERSE else Directions.FORWARD + Direction.REVERSE if direction == DIRECTION_REVERSE else Direction.FORWARD + ) + await self._hub.bond.action( + self._device.device_id, Action.set_direction(bond_direction) ) - self._hub.bond.setDirection(self._device.device_id, bond_direction) diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index f3539416742..daea1c02638 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -1,7 +1,7 @@ """Support for Bond lights.""" from typing import Any, Callable, List, Optional -from bond import DeviceTypes +from bond_api import Action, DeviceType from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -29,13 +29,13 @@ async def async_setup_entry( lights: List[Entity] = [ BondLight(hub, device) for device in hub.devices - if device.type == DeviceTypes.CEILING_FAN and device.supports_light() + if DeviceType.is_fan(device.type) and device.supports_light() ] fireplaces: List[Entity] = [ BondFireplace(hub, device) for device in hub.devices - if device.type == DeviceTypes.FIREPLACE + if DeviceType.is_fireplace(device.type) ] async_add_entities(lights + fireplaces, True) @@ -55,18 +55,18 @@ class BondLight(BondEntity, LightEntity): """Return if light is currently on.""" return self._light == 1 - def update(self): + async def async_update(self): """Fetch assumed state of the light from the hub using API.""" - state: dict = self._hub.bond.getDeviceState(self._device.device_id) + state: dict = await self._hub.bond.device_state(self._device.device_id) self._light = state.get("light") - def turn_on(self, **kwargs: Any) -> None: + async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" - self._hub.bond.turnLightOn(self._device.device_id) + await self._hub.bond.action(self._device.device_id, Action.turn_light_on()) - def turn_off(self, **kwargs: Any) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" - self._hub.bond.turnLightOff(self._device.device_id) + await self._hub.bond.action(self._device.device_id, Action.turn_light_off()) class BondFireplace(BondEntity, LightEntity): @@ -90,18 +90,18 @@ class BondFireplace(BondEntity, LightEntity): """Return True if power is on.""" return self._power == 1 - def turn_on(self, **kwargs: Any) -> None: + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the fireplace on.""" - self._hub.bond.turnOn(self._device.device_id) - brightness = kwargs.get(ATTR_BRIGHTNESS) if brightness: flame = round((brightness * 100) / 255) - self._hub.bond.setFlame(self._device.device_id, flame) + await self._hub.bond.action(self._device.device_id, Action.set_flame(flame)) + else: + await self._hub.bond.action(self._device.device_id, Action.turn_on()) - def turn_off(self, **kwargs: Any) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the fireplace off.""" - self._hub.bond.turnOff(self._device.device_id) + await self._hub.bond.action(self._device.device_id, Action.turn_off()) @property def brightness(self): @@ -113,8 +113,8 @@ class BondFireplace(BondEntity, LightEntity): """Show fireplace icon for the entity.""" return "mdi:fireplace" if self._power == 1 else "mdi:fireplace-off" - def update(self): + async def async_update(self): """Fetch assumed state of the device from the hub using API.""" - state: dict = self._hub.bond.getDeviceState(self._device.device_id) + state: dict = await self._hub.bond.device_state(self._device.device_id) self._power = state.get("power") self._flame = state.get("flame") diff --git a/homeassistant/components/bond/manifest.json b/homeassistant/components/bond/manifest.json index b9e57981400..6b0bae84893 100644 --- a/homeassistant/components/bond/manifest.json +++ b/homeassistant/components/bond/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/bond", "requirements": [ - "bond-home==0.0.9" + "bond-api==0.1.4" ], "codeowners": [ "@prystupa" diff --git a/homeassistant/components/bond/switch.py b/homeassistant/components/bond/switch.py index 4768bbf8eda..3d5b467345e 100644 --- a/homeassistant/components/bond/switch.py +++ b/homeassistant/components/bond/switch.py @@ -1,13 +1,13 @@ """Support for Bond generic devices.""" from typing import Any, Callable, List, Optional -from bond import DeviceTypes +from bond_api import Action, DeviceType +from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity -from ..switch import SwitchEntity from .const import DOMAIN from .entity import BondEntity from .utils import BondDevice, BondHub @@ -24,7 +24,7 @@ async def async_setup_entry( switches = [ BondSwitch(hub, device) for device in hub.devices - if device.type == DeviceTypes.GENERIC_DEVICE + if DeviceType.is_generic(device.type) ] async_add_entities(switches, True) @@ -44,15 +44,15 @@ class BondSwitch(BondEntity, SwitchEntity): """Return True if power is on.""" return self._power == 1 - def turn_on(self, **kwargs: Any) -> None: + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" - self._hub.bond.turnOn(self._device.device_id) + await self._hub.bond.action(self._device.device_id, Action.turn_on()) - def turn_off(self, **kwargs: Any) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Turn the device off.""" - self._hub.bond.turnOff(self._device.device_id) + await self._hub.bond.action(self._device.device_id, Action.turn_off()) - def update(self): + async def async_update(self): """Fetch assumed state of the device from the hub using API.""" - state: dict = self._hub.bond.getDeviceState(self._device.device_id) + state: dict = await self._hub.bond.device_state(self._device.device_id) self._power = state.get("power") diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 5e1360bcd41..545360b25fd 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -2,7 +2,7 @@ from typing import List, Optional -from bond import Actions, Bond +from bond_api import Action, Bond class BondDevice: @@ -27,18 +27,12 @@ class BondDevice: def supports_speed(self) -> bool: """Return True if this device supports any of the speed related commands.""" actions: List[str] = self._attrs["actions"] - return bool([action for action in actions if action in [Actions.SET_SPEED]]) + return bool([action for action in actions if action in [Action.SET_SPEED]]) def supports_direction(self) -> bool: """Return True if this device supports any of the direction related commands.""" actions: List[str] = self._attrs["actions"] - return bool( - [ - action - for action in actions - if action in [Actions.SET_DIRECTION, Actions.TOGGLE_DIRECTION] - ] - ) + return bool([action for action in actions if action in [Action.SET_DIRECTION]]) def supports_light(self) -> bool: """Return True if this device supports any of the light related commands.""" @@ -47,7 +41,7 @@ class BondDevice: [ action for action in actions - if action in [Actions.TURN_LIGHT_ON, Actions.TOGGLE_LIGHT] + if action in [Action.TURN_LIGHT_ON, Action.TURN_LIGHT_OFF] ] ) @@ -61,17 +55,17 @@ class BondHub: self._version: Optional[dict] = None self._devices: Optional[List[BondDevice]] = None - def setup(self): + async def setup(self): """Read hub version information.""" - self._version = self.bond.getVersion() + self._version = await self.bond.version() # Fetch all available devices using Bond API. - device_ids = self.bond.getDeviceIds() + device_ids = await self.bond.devices() self._devices = [ BondDevice( device_id, - self.bond.getDevice(device_id), - self.bond.getProperties(device_id), + await self.bond.device(device_id), + await self.bond.device_properties(device_id), ) for device_id in device_ids ] diff --git a/requirements_all.txt b/requirements_all.txt index aff16a8af3d..63e5d4e044d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -356,7 +356,7 @@ blockchain==1.4.4 bomradarloop==0.1.4 # homeassistant.components.bond -bond-home==0.0.9 +bond-api==0.1.4 # homeassistant.components.amazon_polly # homeassistant.components.route53 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d8ce149b4f0..af0c25268c2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -181,7 +181,7 @@ blinkpy==0.15.1 bomradarloop==0.1.4 # homeassistant.components.bond -bond-home==0.0.9 +bond-api==0.1.4 # homeassistant.components.braviatv bravia-tv==1.0.6 diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index 780e235d5c9..28395bfbe77 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -21,9 +21,7 @@ async def setup_bond_entity( config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.bond.Bond.getVersion", return_value=hub_version - ): + with patch("homeassistant.components.bond.Bond.version", return_value=hub_version): return await hass.config_entries.async_setup(config_entry.entry_id) @@ -45,13 +43,15 @@ async def setup_platform( mock_entry.add_to_hass(hass) with patch("homeassistant.components.bond.PLATFORMS", [platform]), patch( - "homeassistant.components.bond.Bond.getVersion", return_value=MOCK_HUB_VERSION + "homeassistant.components.bond.Bond.version", return_value=MOCK_HUB_VERSION ), patch_bond_device_ids(return_value=[bond_device_id],), patch( - "homeassistant.components.bond.Bond.getDevice", return_value=discovered_device + "homeassistant.components.bond.Bond.device", return_value=discovered_device ), patch_bond_device_state( return_value={} ), patch( - "homeassistant.components.bond.Bond.getProperties", return_value=props + "homeassistant.components.bond.Bond.device_properties", return_value=props + ), patch( + "homeassistant.components.bond.Bond.device_state", return_value={} ): assert await async_setup_component(hass, BOND_DOMAIN, {}) await hass.async_block_till_done() @@ -60,70 +60,25 @@ async def setup_platform( def patch_bond_device_ids(return_value=None): - """Patch Bond API getDeviceIds command.""" + """Patch Bond API devices command.""" if return_value is None: return_value = [] return patch( - "homeassistant.components.bond.Bond.getDeviceIds", return_value=return_value, + "homeassistant.components.bond.Bond.devices", return_value=return_value, ) -def patch_bond_turn_on(): - """Patch Bond API turnOn command.""" - return patch("homeassistant.components.bond.Bond.turnOn") - - -def patch_bond_turn_off(): - """Patch Bond API turnOff command.""" - return patch("homeassistant.components.bond.Bond.turnOff") - - -def patch_bond_set_speed(): - """Patch Bond API setSpeed command.""" - return patch("homeassistant.components.bond.Bond.setSpeed") - - -def patch_bond_set_flame(): - """Patch Bond API setFlame command.""" - return patch("homeassistant.components.bond.Bond.setFlame") - - -def patch_bond_open(): - """Patch Bond API open command.""" - return patch("homeassistant.components.bond.Bond.open") - - -def patch_bond_close(): - """Patch Bond API close command.""" - return patch("homeassistant.components.bond.Bond.close") - - -def patch_bond_hold(): - """Patch Bond API hold command.""" - return patch("homeassistant.components.bond.Bond.hold") - - -def patch_bond_set_direction(): - """Patch Bond API setDirection command.""" - return patch("homeassistant.components.bond.Bond.setDirection") - - -def patch_turn_light_on(): - """Patch Bond API turnLightOn command.""" - return patch("homeassistant.components.bond.Bond.turnLightOn") - - -def patch_turn_light_off(): - """Patch Bond API turnLightOff command.""" - return patch("homeassistant.components.bond.Bond.turnLightOff") +def patch_bond_action(): + """Patch Bond API action command.""" + return patch("homeassistant.components.bond.Bond.action") def patch_bond_device_state(return_value=None): - """Patch Bond API getDeviceState command.""" + """Patch Bond API device state endpoint.""" if return_value is None: return_value = {} return patch( - "homeassistant.components.bond.Bond.getDeviceState", return_value=return_value + "homeassistant.components.bond.Bond.device_state", return_value=return_value ) diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py index 94b98b45d6f..825215207a0 100644 --- a/tests/components/bond/test_config_flow.py +++ b/tests/components/bond/test_config_flow.py @@ -1,13 +1,12 @@ """Test the Bond config flow.""" -from json import JSONDecodeError -from requests.exceptions import ConnectionError +from aiohttp import ClientConnectionError, ClientResponseError from homeassistant import config_entries, core, setup from homeassistant.components.bond.const import DOMAIN from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST -from tests.async_mock import patch +from tests.async_mock import Mock, patch async def test_form(hass: core.HomeAssistant): @@ -20,7 +19,7 @@ async def test_form(hass: core.HomeAssistant): assert result["errors"] == {} with patch( - "homeassistant.components.bond.config_flow.Bond.getDeviceIds", return_value=[], + "homeassistant.components.bond.config_flow.Bond.devices", return_value=[], ), patch( "homeassistant.components.bond.async_setup", return_value=True ) as mock_setup, patch( @@ -48,8 +47,8 @@ async def test_form_invalid_auth(hass: core.HomeAssistant): ) with patch( - "homeassistant.components.bond.config_flow.Bond.getDeviceIds", - side_effect=JSONDecodeError("test-message", "test-doc", 0), + "homeassistant.components.bond.config_flow.Bond.devices", + side_effect=ClientResponseError(Mock(), Mock(), status=401), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, @@ -66,8 +65,8 @@ async def test_form_cannot_connect(hass: core.HomeAssistant): ) with patch( - "homeassistant.components.bond.config_flow.Bond.getDeviceIds", - side_effect=ConnectionError, + "homeassistant.components.bond.config_flow.Bond.devices", + side_effect=ClientConnectionError(), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, @@ -84,8 +83,8 @@ async def test_form_unexpected_error(hass: core.HomeAssistant): ) with patch( - "homeassistant.components.bond.config_flow.Bond.getDeviceIds", - side_effect=Exception, + "homeassistant.components.bond.config_flow.Bond.devices", + side_effect=ClientResponseError(Mock(), Mock(), status=500), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, diff --git a/tests/components/bond/test_cover.py b/tests/components/bond/test_cover.py index dacb612add9..da73e086a61 100644 --- a/tests/components/bond/test_cover.py +++ b/tests/components/bond/test_cover.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from bond import DeviceTypes +from bond_api import Action, DeviceType from homeassistant import core from homeassistant.components.cover import DOMAIN as COVER_DOMAIN @@ -15,13 +15,7 @@ from homeassistant.const import ( from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow -from .common import ( - patch_bond_close, - patch_bond_device_state, - patch_bond_hold, - patch_bond_open, - setup_platform, -) +from .common import patch_bond_action, patch_bond_device_state, setup_platform from tests.common import async_fire_time_changed @@ -30,7 +24,7 @@ _LOGGER = logging.getLogger(__name__) def shades(name: str): """Create motorized shades with given name.""" - return {"name": name, "type": DeviceTypes.MOTORIZED_SHADES} + return {"name": name, "type": DeviceType.MOTORIZED_SHADES} async def test_entity_registry(hass: core.HomeAssistant): @@ -43,9 +37,11 @@ async def test_entity_registry(hass: core.HomeAssistant): async def test_open_cover(hass: core.HomeAssistant): """Tests that open cover command delegates to API.""" - await setup_platform(hass, COVER_DOMAIN, shades("name-1")) + await setup_platform( + hass, COVER_DOMAIN, shades("name-1"), bond_device_id="test-device-id" + ) - with patch_bond_open() as mock_open, patch_bond_device_state(): + with patch_bond_action() as mock_open, patch_bond_device_state(): await hass.services.async_call( COVER_DOMAIN, SERVICE_OPEN_COVER, @@ -53,14 +49,17 @@ async def test_open_cover(hass: core.HomeAssistant): blocking=True, ) await hass.async_block_till_done() - mock_open.assert_called_once() + + mock_open.assert_called_once_with("test-device-id", Action.open()) async def test_close_cover(hass: core.HomeAssistant): """Tests that close cover command delegates to API.""" - await setup_platform(hass, COVER_DOMAIN, shades("name-1")) + await setup_platform( + hass, COVER_DOMAIN, shades("name-1"), bond_device_id="test-device-id" + ) - with patch_bond_close() as mock_close, patch_bond_device_state(): + with patch_bond_action() as mock_close, patch_bond_device_state(): await hass.services.async_call( COVER_DOMAIN, SERVICE_CLOSE_COVER, @@ -68,14 +67,17 @@ async def test_close_cover(hass: core.HomeAssistant): blocking=True, ) await hass.async_block_till_done() - mock_close.assert_called_once() + + mock_close.assert_called_once_with("test-device-id", Action.close()) async def test_stop_cover(hass: core.HomeAssistant): """Tests that stop cover command delegates to API.""" - await setup_platform(hass, COVER_DOMAIN, shades("name-1")) + await setup_platform( + hass, COVER_DOMAIN, shades("name-1"), bond_device_id="test-device-id" + ) - with patch_bond_hold() as mock_hold, patch_bond_device_state(): + with patch_bond_action() as mock_hold, patch_bond_device_state(): await hass.services.async_call( COVER_DOMAIN, SERVICE_STOP_COVER, @@ -83,7 +85,8 @@ async def test_stop_cover(hass: core.HomeAssistant): blocking=True, ) await hass.async_block_till_done() - mock_hold.assert_called_once() + + mock_hold.assert_called_once_with("test-device-id", Action.hold()) async def test_update_reports_open_cover(hass: core.HomeAssistant): diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index b518f72f326..f73310bc504 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -1,7 +1,8 @@ """Tests for the Bond fan device.""" from datetime import timedelta +from typing import Optional -from bond import DeviceTypes, Directions +from bond_api import Action, DeviceType, Direction from homeassistant import core from homeassistant.components import fan @@ -17,14 +18,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_O from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow -from .common import ( - patch_bond_device_state, - patch_bond_set_direction, - patch_bond_set_speed, - patch_bond_turn_off, - patch_bond_turn_on, - setup_platform, -) +from .common import patch_bond_action, patch_bond_device_state, setup_platform from tests.common import async_fire_time_changed @@ -33,18 +27,20 @@ def ceiling_fan(name: str): """Create a ceiling fan with given name.""" return { "name": name, - "type": DeviceTypes.CEILING_FAN, + "type": DeviceType.CEILING_FAN, "actions": ["SetSpeed", "SetDirection"], } -async def turn_fan_on(hass: core.HomeAssistant, fan_id: str, speed: str) -> None: +async def turn_fan_on( + hass: core.HomeAssistant, fan_id: str, speed: Optional[str] = None +) -> None: """Turn the fan on at the specified speed.""" + service_data = {ATTR_ENTITY_ID: fan_id} + if speed: + service_data[fan.ATTR_SPEED] = speed await hass.services.async_call( - FAN_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: fan_id, fan.ATTR_SPEED: speed}, - blocking=True, + FAN_DOMAIN, SERVICE_TURN_ON, service_data=service_data, blocking=True, ) await hass.async_block_till_done() @@ -57,7 +53,7 @@ async def test_entity_registry(hass: core.HomeAssistant): assert [key for key in registry.entities] == ["fan.name_1"] -async def test_entity_non_standard_speed_list(hass: core.HomeAssistant): +async def test_non_standard_speed_list(hass: core.HomeAssistant): """Tests that the device is registered with custom speed list if number of supported speeds differs form 3.""" await setup_platform( hass, @@ -76,41 +72,62 @@ async def test_entity_non_standard_speed_list(hass: core.HomeAssistant): ] with patch_bond_device_state(): - with patch_bond_turn_on(), patch_bond_set_speed() as mock_set_speed_low: + with patch_bond_action() as mock_set_speed_low: await turn_fan_on(hass, "fan.name_1", fan.SPEED_LOW) - mock_set_speed_low.assert_called_once_with("test-device-id", speed=1) + mock_set_speed_low.assert_called_once_with( + "test-device-id", Action.set_speed(1) + ) - with patch_bond_turn_on(), patch_bond_set_speed() as mock_set_speed_medium: + with patch_bond_action() as mock_set_speed_medium: await turn_fan_on(hass, "fan.name_1", fan.SPEED_MEDIUM) - mock_set_speed_medium.assert_called_once_with("test-device-id", speed=3) + mock_set_speed_medium.assert_called_once_with( + "test-device-id", Action.set_speed(3) + ) - with patch_bond_turn_on(), patch_bond_set_speed() as mock_set_speed_high: + with patch_bond_action() as mock_set_speed_high: await turn_fan_on(hass, "fan.name_1", fan.SPEED_HIGH) - mock_set_speed_high.assert_called_once_with("test-device-id", speed=6) + mock_set_speed_high.assert_called_once_with( + "test-device-id", Action.set_speed(6) + ) -async def test_turn_on_fan(hass: core.HomeAssistant): - """Tests that turn on command delegates to API.""" - await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1")) +async def test_turn_on_fan_with_speed(hass: core.HomeAssistant): + """Tests that turn on command delegates to set speed API.""" + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) - with patch_bond_turn_on() as mock_turn_on, patch_bond_set_speed() as mock_set_speed, patch_bond_device_state(): + with patch_bond_action() as mock_set_speed, patch_bond_device_state(): await turn_fan_on(hass, "fan.name_1", fan.SPEED_LOW) - mock_set_speed.assert_called_once() - mock_turn_on.assert_called_once() + mock_set_speed.assert_called_with("test-device-id", Action.set_speed(1)) + + +async def test_turn_on_fan_without_speed(hass: core.HomeAssistant): + """Tests that turn on command delegates to turn on API.""" + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) + + with patch_bond_action() as mock_turn_on, patch_bond_device_state(): + await turn_fan_on(hass, "fan.name_1") + + mock_turn_on.assert_called_with("test-device-id", Action.turn_on()) async def test_turn_off_fan(hass: core.HomeAssistant): """Tests that turn off command delegates to API.""" - await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1")) + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) - with patch_bond_turn_off() as mock_turn_off, patch_bond_device_state(): + with patch_bond_action() as mock_turn_off, patch_bond_device_state(): await hass.services.async_call( FAN_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "fan.name_1"}, blocking=True, ) await hass.async_block_till_done() - mock_turn_off.assert_called_once() + mock_turn_off.assert_called_once_with("test-device-id", Action.turn_off()) async def test_update_reports_fan_on(hass: core.HomeAssistant): @@ -139,7 +156,7 @@ async def test_update_reports_direction_forward(hass: core.HomeAssistant): """Tests that update command sets correct direction when Bond API reports fan direction is forward.""" await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1")) - with patch_bond_device_state(return_value={"direction": Directions.FORWARD}): + with patch_bond_device_state(return_value={"direction": Direction.FORWARD}): async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) await hass.async_block_till_done() @@ -150,7 +167,7 @@ async def test_update_reports_direction_reverse(hass: core.HomeAssistant): """Tests that update command sets correct direction when Bond API reports fan direction is reverse.""" await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1")) - with patch_bond_device_state(return_value={"direction": Directions.REVERSE}): + with patch_bond_device_state(return_value={"direction": Direction.REVERSE}): async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) await hass.async_block_till_done() @@ -159,9 +176,11 @@ async def test_update_reports_direction_reverse(hass: core.HomeAssistant): async def test_set_fan_direction(hass: core.HomeAssistant): """Tests that set direction command delegates to API.""" - await setup_platform(hass, FAN_DOMAIN, ceiling_fan("name-1")) + await setup_platform( + hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) - with patch_bond_set_direction() as mock_set_direction, patch_bond_device_state(): + with patch_bond_action() as mock_set_direction, patch_bond_device_state(): await hass.services.async_call( FAN_DOMAIN, SERVICE_SET_DIRECTION, @@ -169,4 +188,7 @@ async def test_set_fan_direction(hass: core.HomeAssistant): blocking=True, ) await hass.async_block_till_done() - mock_set_direction.assert_called_once() + + mock_set_direction.assert_called_once_with( + "test-device-id", Action.set_direction(Direction.FORWARD) + ) diff --git a/tests/components/bond/test_light.py b/tests/components/bond/test_light.py index edcdc3e63bd..55936e3a11c 100644 --- a/tests/components/bond/test_light.py +++ b/tests/components/bond/test_light.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from bond import Actions, DeviceTypes +from bond_api import Action, DeviceType from homeassistant import core from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN @@ -10,15 +10,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_O from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow -from .common import ( - patch_bond_device_state, - patch_bond_set_flame, - patch_bond_turn_off, - patch_bond_turn_on, - patch_turn_light_off, - patch_turn_light_on, - setup_platform, -) +from .common import patch_bond_action, patch_bond_device_state, setup_platform from tests.common import async_fire_time_changed @@ -29,14 +21,14 @@ def ceiling_fan(name: str): """Create a ceiling fan (that has built-in light) with given name.""" return { "name": name, - "type": DeviceTypes.CEILING_FAN, - "actions": [Actions.TOGGLE_LIGHT], + "type": DeviceType.CEILING_FAN, + "actions": [Action.TURN_LIGHT_ON, Action.TURN_LIGHT_OFF], } def fireplace(name: str): """Create a fireplace with given name.""" - return {"name": name, "type": DeviceTypes.FIREPLACE} + return {"name": name, "type": DeviceType.FIREPLACE} async def test_entity_registry(hass: core.HomeAssistant): @@ -49,9 +41,11 @@ async def test_entity_registry(hass: core.HomeAssistant): async def test_turn_on_light(hass: core.HomeAssistant): """Tests that turn on command delegates to API.""" - await setup_platform(hass, LIGHT_DOMAIN, ceiling_fan("name-1")) + await setup_platform( + hass, LIGHT_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) - with patch_turn_light_on() as mock_turn_light_on, patch_bond_device_state(): + with patch_bond_action() as mock_turn_light_on, patch_bond_device_state(): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -59,14 +53,17 @@ async def test_turn_on_light(hass: core.HomeAssistant): blocking=True, ) await hass.async_block_till_done() - mock_turn_light_on.assert_called_once() + + mock_turn_light_on.assert_called_once_with("test-device-id", Action.turn_light_on()) async def test_turn_off_light(hass: core.HomeAssistant): """Tests that turn off command delegates to API.""" - await setup_platform(hass, LIGHT_DOMAIN, ceiling_fan("name-1")) + await setup_platform( + hass, LIGHT_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" + ) - with patch_turn_light_off() as mock_turn_light_off, patch_bond_device_state(): + with patch_bond_action() as mock_turn_light_off, patch_bond_device_state(): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, @@ -74,7 +71,10 @@ async def test_turn_off_light(hass: core.HomeAssistant): blocking=True, ) await hass.async_block_till_done() - mock_turn_light_off.assert_called_once() + + mock_turn_light_off.assert_called_once_with( + "test-device-id", Action.turn_light_off() + ) async def test_update_reports_light_is_on(hass: core.HomeAssistant): @@ -99,13 +99,13 @@ async def test_update_reports_light_is_off(hass: core.HomeAssistant): assert hass.states.get("light.name_1").state == "off" -async def test_turn_on_fireplace(hass: core.HomeAssistant): - """Tests that turn on command delegates to API.""" +async def test_turn_on_fireplace_with_brightness(hass: core.HomeAssistant): + """Tests that turn on command delegates to set flame API.""" await setup_platform( hass, LIGHT_DOMAIN, fireplace("name-1"), bond_device_id="test-device-id" ) - with patch_bond_turn_on() as mock_turn_on, patch_bond_set_flame() as mock_set_flame, patch_bond_device_state(): + with patch_bond_action() as mock_set_flame, patch_bond_device_state(): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -114,15 +114,34 @@ async def test_turn_on_fireplace(hass: core.HomeAssistant): ) await hass.async_block_till_done() - mock_turn_on.assert_called_once() - mock_set_flame.assert_called_once_with("test-device-id", 50) + mock_set_flame.assert_called_once_with("test-device-id", Action.set_flame(50)) + + +async def test_turn_on_fireplace_without_brightness(hass: core.HomeAssistant): + """Tests that turn on command delegates to turn on API.""" + await setup_platform( + hass, LIGHT_DOMAIN, fireplace("name-1"), bond_device_id="test-device-id" + ) + + with patch_bond_action() as mock_turn_on, patch_bond_device_state(): + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.name_1"}, + blocking=True, + ) + await hass.async_block_till_done() + + mock_turn_on.assert_called_once_with("test-device-id", Action.turn_on()) async def test_turn_off_fireplace(hass: core.HomeAssistant): """Tests that turn off command delegates to API.""" - await setup_platform(hass, LIGHT_DOMAIN, fireplace("name-1")) + await setup_platform( + hass, LIGHT_DOMAIN, fireplace("name-1"), bond_device_id="test-device-id" + ) - with patch_bond_turn_off() as mock_turn_off, patch_bond_device_state(): + with patch_bond_action() as mock_turn_off, patch_bond_device_state(): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, @@ -130,7 +149,8 @@ async def test_turn_off_fireplace(hass: core.HomeAssistant): blocking=True, ) await hass.async_block_till_done() - mock_turn_off.assert_called_once() + + mock_turn_off.assert_called_once_with("test-device-id", Action.turn_off()) async def test_flame_converted_to_brightness(hass: core.HomeAssistant): @@ -141,5 +161,4 @@ async def test_flame_converted_to_brightness(hass: core.HomeAssistant): async_fire_time_changed(hass, utcnow() + timedelta(seconds=30)) await hass.async_block_till_done() - _LOGGER.warning(hass.states.get("light.name_1").attributes) assert hass.states.get("light.name_1").attributes[ATTR_BRIGHTNESS] == 128 diff --git a/tests/components/bond/test_switch.py b/tests/components/bond/test_switch.py index b2d77150907..1cfdf682d38 100644 --- a/tests/components/bond/test_switch.py +++ b/tests/components/bond/test_switch.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from bond import DeviceTypes +from bond_api import Action, DeviceType from homeassistant import core from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN @@ -10,12 +10,7 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_O from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.util import utcnow -from .common import ( - patch_bond_device_state, - patch_bond_turn_off, - patch_bond_turn_on, - setup_platform, -) +from .common import patch_bond_action, patch_bond_device_state, setup_platform from tests.common import async_fire_time_changed @@ -24,7 +19,7 @@ _LOGGER = logging.getLogger(__name__) def generic_device(name: str): """Create a generic device with given name.""" - return {"name": name, "type": DeviceTypes.GENERIC_DEVICE} + return {"name": name, "type": DeviceType.GENERIC_DEVICE} async def test_entity_registry(hass: core.HomeAssistant): @@ -37,9 +32,11 @@ async def test_entity_registry(hass: core.HomeAssistant): async def test_turn_on_switch(hass: core.HomeAssistant): """Tests that turn on command delegates to API.""" - await setup_platform(hass, SWITCH_DOMAIN, generic_device("name-1")) + await setup_platform( + hass, SWITCH_DOMAIN, generic_device("name-1"), bond_device_id="test-device-id" + ) - with patch_bond_turn_on() as mock_turn_on, patch_bond_device_state(): + with patch_bond_action() as mock_turn_on, patch_bond_device_state(): await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, @@ -47,14 +44,17 @@ async def test_turn_on_switch(hass: core.HomeAssistant): blocking=True, ) await hass.async_block_till_done() - mock_turn_on.assert_called_once() + + mock_turn_on.assert_called_once_with("test-device-id", Action.turn_on()) async def test_turn_off_switch(hass: core.HomeAssistant): """Tests that turn off command delegates to API.""" - await setup_platform(hass, SWITCH_DOMAIN, generic_device("name-1")) + await setup_platform( + hass, SWITCH_DOMAIN, generic_device("name-1"), bond_device_id="test-device-id" + ) - with patch_bond_turn_off() as mock_turn_off, patch_bond_device_state(): + with patch_bond_action() as mock_turn_off, patch_bond_device_state(): await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_OFF, @@ -62,7 +62,8 @@ async def test_turn_off_switch(hass: core.HomeAssistant): blocking=True, ) await hass.async_block_till_done() - mock_turn_off.assert_called_once() + + mock_turn_off.assert_called_once_with("test-device-id", Action.turn_off()) async def test_update_reports_switch_is_on(hass: core.HomeAssistant):