From 9b77e16ffc3e6b4398befc7ec640d72fe6d2797a Mon Sep 17 00:00:00 2001 From: Eugene Prystupa Date: Sun, 5 Jul 2020 21:17:53 -0400 Subject: [PATCH] Add new integration for Bond hub (#37477) * create foundation for Bond integration * add Bond hub integration (fix lint) * Update homeassistant/components/bond/__init__.py adding async_unload_entry per PR review suggestion Co-authored-by: Chris Talkington * add Bond hub integration (fix missing import after applying PR suggestion) * Update tests/components/bond/test_init.py add a unit for unloading per PR review suggestion Co-authored-by: Chris Talkington * Update tests/components/bond/test_init.py add unit test for unload per PR review suggestion Co-authored-by: Chris Talkington * Update tests/components/bond/test_init.py PR review suggestion Co-authored-by: Chris Talkington * Update tests/components/bond/test_init.py PR review suggestion Co-authored-by: Chris Talkington * Update tests/components/bond/test_init.py PR review suggestion Co-authored-by: Chris Talkington * add Bond hub integration (fix formatting) * Update homeassistant/components/bond/manifest.json PR suggestion Co-authored-by: Chris Talkington * Update homeassistant/components/bond/manifest.json PR suggestion Co-authored-by: Chris Talkington * Update homeassistant/components/bond/manifest.json PR suggestion Co-authored-by: Chris Talkington * Update homeassistant/components/bond/strings.json PR suggestion Co-authored-by: Chris Talkington * Update homeassistant/components/bond/manifest.json PR suggestion Co-authored-by: Chris Talkington * Update requirements_all.txt PR suggestion Co-authored-by: Chris Talkington * Update homeassistant/components/bond/manifest.json PR suggestion Co-authored-by: Chris Talkington * Update requirements_test_all.txt PR suggestion Co-authored-by: Chris Talkington * add Bond hub integration (remove friendly name from config per PR suggestion) * Update homeassistant/components/bond/__init__.py add per PR review feedback Co-authored-by: Chris Talkington * Update homeassistant/components/bond/__init__.py remove per PR review feedback Co-authored-by: Chris Talkington * Update tests/components/bond/test_init.py fix unit test Co-authored-by: Chris Talkington Co-authored-by: Chris Talkington --- CODEOWNERS | 1 + homeassistant/components/bond/__init__.py | 53 ++++++++ homeassistant/components/bond/config_flow.py | 74 ++++++++++++ homeassistant/components/bond/const.py | 3 + homeassistant/components/bond/cover.py | 84 +++++++++++++ homeassistant/components/bond/manifest.json | 12 ++ homeassistant/components/bond/strings.json | 17 +++ .../components/bond/translations/en.json | 18 +++ homeassistant/components/bond/utils.py | 38 ++++++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/bond/__init__.py | 1 + tests/components/bond/common.py | 22 ++++ tests/components/bond/test_config_flow.py | 94 +++++++++++++++ tests/components/bond/test_cover.py | 113 ++++++++++++++++++ tests/components/bond/test_init.py | 56 +++++++++ tests/components/bond/test_utils.py | 24 ++++ 18 files changed, 617 insertions(+) create mode 100644 homeassistant/components/bond/__init__.py create mode 100644 homeassistant/components/bond/config_flow.py create mode 100644 homeassistant/components/bond/const.py create mode 100644 homeassistant/components/bond/cover.py create mode 100644 homeassistant/components/bond/manifest.json create mode 100644 homeassistant/components/bond/strings.json create mode 100644 homeassistant/components/bond/translations/en.json create mode 100644 homeassistant/components/bond/utils.py create mode 100644 tests/components/bond/__init__.py create mode 100644 tests/components/bond/common.py create mode 100644 tests/components/bond/test_config_flow.py create mode 100644 tests/components/bond/test_cover.py create mode 100644 tests/components/bond/test_init.py create mode 100644 tests/components/bond/test_utils.py diff --git a/CODEOWNERS b/CODEOWNERS index 71c10ab0bcc..69e622b373d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -58,6 +58,7 @@ homeassistant/components/blink/* @fronzbot homeassistant/components/bmp280/* @belidzs homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe homeassistant/components/bom/* @maddenp +homeassistant/components/bond/* @prystupa homeassistant/components/braviatv/* @bieniu homeassistant/components/broadlink/* @danielhiversen @felipediel homeassistant/components/brother/* @bieniu diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py new file mode 100644 index 00000000000..199841673bf --- /dev/null +++ b/homeassistant/components/bond/__init__.py @@ -0,0 +1,53 @@ +"""The Bond integration.""" +import asyncio + +from bond import Bond +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST +from homeassistant.core import HomeAssistant + +from .const import DOMAIN + +CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) + +PLATFORMS = ["cover"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Bond component.""" + hass.data.setdefault(DOMAIN, {}) + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Bond from a config entry.""" + host = entry.data[CONF_HOST] + token = entry.data[CONF_ACCESS_TOKEN] + + hass.data[DOMAIN][entry.entry_id] = Bond(bondIp=host, bondToken=token) + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py new file mode 100644 index 00000000000..0ee633b79bb --- /dev/null +++ b/homeassistant/components/bond/config_flow.py @@ -0,0 +1,74 @@ +"""Config flow for Bond integration.""" +import logging + +from bond import Bond +from requests.exceptions import ConnectionError as RequestConnectionError +from simplejson import JSONDecodeError +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST + +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema( + {vol.Required(CONF_HOST): str, vol.Required(CONF_ACCESS_TOKEN): str} +) + + +async def validate_input(hass: core.HomeAssistant, 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 + + 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 {"title": data[CONF_HOST]} + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Bond.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + if user_input is not None: + try: + info = await validate_input(self.hass, user_input) + + return self.async_create_entry(title=info["title"], data=user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/bond/const.py b/homeassistant/components/bond/const.py new file mode 100644 index 00000000000..4ad08991b31 --- /dev/null +++ b/homeassistant/components/bond/const.py @@ -0,0 +1,3 @@ +"""Constants for the Bond integration.""" + +DOMAIN = "bond" diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py new file mode 100644 index 00000000000..149ad8b9225 --- /dev/null +++ b/homeassistant/components/bond/cover.py @@ -0,0 +1,84 @@ +"""Support for Bond covers.""" +import asyncio +import logging +from typing import Any, Callable, Dict, List, Optional + +from bond import BOND_DEVICE_TYPE_MOTORIZED_SHADES, Bond + +from homeassistant.components.cover import DEVICE_CLASS_SHADE, CoverEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_NAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN +from .utils import BondDevice, get_bond_devices + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: Callable[[List[Entity]], None], +) -> None: + """Set up Bond cover devices.""" + + bond: Bond = hass.data[DOMAIN][entry.entry_id] + + async def discover(): + devices = await get_bond_devices(hass, bond) + covers = [ + BondCover(bond, device) + for device in devices + if device.type == BOND_DEVICE_TYPE_MOTORIZED_SHADES + ] + async_add_entities(covers) + + asyncio.create_task(discover()) + + +class BondCover(CoverEntity): + """Representation of a Bond cover.""" + + def __init__(self, bond: Bond, device: BondDevice): + """Create HA entity representing Bond cover.""" + self._bond = bond + self._device = device + + @property + def device_class(self) -> Optional[str]: + """Get device class.""" + return DEVICE_CLASS_SHADE + + @property + def unique_id(self) -> Optional[str]: + """Get unique ID for the entity.""" + return self._device.device_id + + @property + def name(self) -> Optional[str]: + """Get entity name.""" + return self._device.name + + @property + def device_info(self) -> Optional[Dict[str, Any]]: + """Get a an HA device representing this cover.""" + return {ATTR_NAME: self.name, "identifiers": {(DOMAIN, self.unique_id)}} + + @property + def is_closed(self): + """Return if the cover is closed or not.""" + return None + + def open_cover(self, **kwargs: Any) -> None: + """Open the cover.""" + self._bond.open(self._device.device_id) + + def close_cover(self, **kwargs: Any) -> None: + """Close cover.""" + self._bond.close(self._device.device_id) + + def stop_cover(self, **kwargs): + """Hold cover.""" + self._bond.hold(self._device.device_id) diff --git a/homeassistant/components/bond/manifest.json b/homeassistant/components/bond/manifest.json new file mode 100644 index 00000000000..a9a7e3f1e95 --- /dev/null +++ b/homeassistant/components/bond/manifest.json @@ -0,0 +1,12 @@ +{ + "domain": "bond", + "name": "Bond", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/bond", + "requirements": [ + "bond-home==0.0.8" + ], + "codeowners": [ + "@prystupa" + ] +} diff --git a/homeassistant/components/bond/strings.json b/homeassistant/components/bond/strings.json new file mode 100644 index 00000000000..a243c938f12 --- /dev/null +++ b/homeassistant/components/bond/strings.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "access_token": "[%key:common::config_flow::data::access_token%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + } +} diff --git a/homeassistant/components/bond/translations/en.json b/homeassistant/components/bond/translations/en.json new file mode 100644 index 00000000000..6c03562ba92 --- /dev/null +++ b/homeassistant/components/bond/translations/en.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Unable to connect to Bond device", + "invalid_auth": "Unable to authenticate with Bond device, check provided token", + "unknown": "Unknown error while connecting to Bond device (check the logs for errors)" + }, + "step": { + "user": { + "data": { + "host": "Bond device IP address or host name", + "access_token": "Bond device token" + } + } + } + }, + "title": "Bond" +} diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py new file mode 100644 index 00000000000..e352a9b7ffb --- /dev/null +++ b/homeassistant/components/bond/utils.py @@ -0,0 +1,38 @@ +"""Reusable utilities for the Bond component.""" + +from typing import List + +from bond import Bond + +from homeassistant.core import HomeAssistant + + +class BondDevice: + """Helper device class to hold ID and attributes together.""" + + def __init__(self, device_id: str, attrs: dict): + """Create a helper device from ID and attributes returned by API.""" + self.device_id = device_id + self._attrs = attrs + + @property + def name(self): + """Get the name of this device.""" + return self._attrs["name"] + + @property + def type(self): + """Get the type of this device.""" + return self._attrs["type"] + + +async def get_bond_devices(hass: HomeAssistant, bond: Bond) -> List[BondDevice]: + """Fetch all available devices using Bond API.""" + device_ids = await hass.async_add_executor_job(bond.getDeviceIds) + devices = [ + BondDevice( + device_id, await hass.async_add_executor_job(bond.getDevice, device_id) + ) + for device_id in device_ids + ] + return devices diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index c7838063187..67b72b20aff 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -23,6 +23,7 @@ FLOWS = [ "axis", "blebox", "blink", + "bond", "braviatv", "brother", "bsblan", diff --git a/requirements_all.txt b/requirements_all.txt index 1634e71967b..2e992212ac8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -370,6 +370,9 @@ blockchain==1.4.4 # homeassistant.components.bom bomradarloop==0.1.4 +# homeassistant.components.bond +bond-home==0.0.8 + # homeassistant.components.amazon_polly # homeassistant.components.route53 boto3==1.9.252 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e7834c665a4..40d7ef64d7f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -176,6 +176,9 @@ blinkpy==0.15.0 # homeassistant.components.bom bomradarloop==0.1.4 +# homeassistant.components.bond +bond-home==0.0.8 + # homeassistant.components.braviatv bravia-tv==1.0.6 diff --git a/tests/components/bond/__init__.py b/tests/components/bond/__init__.py new file mode 100644 index 00000000000..62d554c9893 --- /dev/null +++ b/tests/components/bond/__init__.py @@ -0,0 +1 @@ +"""Tests for the Bond integration.""" diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py new file mode 100644 index 00000000000..b315f7e86fd --- /dev/null +++ b/tests/components/bond/common.py @@ -0,0 +1,22 @@ +"""Common methods used across tests for Bond.""" +from homeassistant.components.bond.const import DOMAIN as BOND_DOMAIN +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST +from homeassistant.setup import async_setup_component + +from tests.async_mock import patch +from tests.common import MockConfigEntry + + +async def setup_platform(hass, platform): + """Set up the specified Bond platform.""" + mock_entry = MockConfigEntry( + domain=BOND_DOMAIN, + data={CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, + ) + mock_entry.add_to_hass(hass) + + with patch("homeassistant.components.bond.PLATFORMS", [platform]): + assert await async_setup_component(hass, BOND_DOMAIN, {}) + await hass.async_block_till_done() + + return mock_entry diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py new file mode 100644 index 00000000000..e6db5671ef8 --- /dev/null +++ b/tests/components/bond/test_config_flow.py @@ -0,0 +1,94 @@ +"""Test the Bond config flow.""" +from requests.exceptions import ConnectionError +from simplejson import JSONDecodeError + +from homeassistant import config_entries, setup +from homeassistant.components.bond.const import DOMAIN +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST + +from tests.async_mock import patch + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.bond.config_flow.Bond.getDeviceIds", return_value=[], + ), patch( + "homeassistant.components.bond.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.bond.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "1.1.1.1" + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + CONF_ACCESS_TOKEN: "test-token", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.bond.config_flow.Bond.getDeviceIds", + side_effect=JSONDecodeError("test-message", "test-doc", 0), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_cannot_connect(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.bond.config_flow.Bond.getDeviceIds", + side_effect=ConnectionError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_unexpected_error(hass): + """Test we handle unexpected error gracefully.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.bond.config_flow.Bond.getDeviceIds", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} diff --git a/tests/components/bond/test_cover.py b/tests/components/bond/test_cover.py new file mode 100644 index 00000000000..51f7f4f0999 --- /dev/null +++ b/tests/components/bond/test_cover.py @@ -0,0 +1,113 @@ +"""Tests for the Bond cover device.""" +import logging + +from bond import BOND_DEVICE_TYPE_MOTORIZED_SHADES + +from homeassistant.components.bond.utils import BondDevice +from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_CLOSE_COVER, + SERVICE_OPEN_COVER, + SERVICE_STOP_COVER, +) +from homeassistant.helpers.entity_registry import EntityRegistry + +from .common import setup_platform + +from tests.async_mock import patch + +_LOGGER = logging.getLogger(__name__) + + +async def test_entity_registry(hass): + """Tests that the devices are registered in the entity registry.""" + + with patch( + "homeassistant.components.bond.utils.get_bond_devices", + return_value=[ + BondDevice( + "device-1", + {"name": "name-1", "type": BOND_DEVICE_TYPE_MOTORIZED_SHADES}, + ) + ], + ): + await setup_platform(hass, COVER_DOMAIN) + + registry: EntityRegistry = await hass.helpers.entity_registry.async_get_registry() + assert [key for key in registry.entities.keys()] == ["cover.name_1"] + + +async def test_open_cover(hass): + """Tests that open cover command delegates to API.""" + + with patch( + "homeassistant.components.bond.utils.get_bond_devices", + return_value=[ + BondDevice( + "device-1", + {"name": "name-1", "type": BOND_DEVICE_TYPE_MOTORIZED_SHADES}, + ) + ], + ): + await setup_platform(hass, COVER_DOMAIN) + + with patch("bond.Bond.open") as mock_open: + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: "cover.name_1"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_open.assert_called_once() + + +async def test_close_cover(hass): + """Tests that close cover command delegates to API.""" + + with patch( + "homeassistant.components.bond.utils.get_bond_devices", + return_value=[ + BondDevice( + "device-1", + {"name": "name-1", "type": BOND_DEVICE_TYPE_MOTORIZED_SHADES}, + ) + ], + ): + await setup_platform(hass, COVER_DOMAIN) + + with patch("bond.Bond.close") as mock_close: + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: "cover.name_1"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_close.assert_called_once() + + +async def test_stop_cover(hass): + """Tests that stop cover command delegates to API.""" + + with patch( + "homeassistant.components.bond.utils.get_bond_devices", + return_value=[ + BondDevice( + "device-1", + {"name": "name-1", "type": BOND_DEVICE_TYPE_MOTORIZED_SHADES}, + ) + ], + ): + await setup_platform(hass, COVER_DOMAIN) + + with patch("bond.Bond.hold") as mock_hold: + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_STOP_COVER, + {ATTR_ENTITY_ID: "cover.name_1"}, + blocking=True, + ) + await hass.async_block_till_done() + mock_hold.assert_called_once() diff --git a/tests/components/bond/test_init.py b/tests/components/bond/test_init.py new file mode 100644 index 00000000000..0036b840b7f --- /dev/null +++ b/tests/components/bond/test_init.py @@ -0,0 +1,56 @@ +"""Tests for the Bond module.""" +from homeassistant.components.bond.const import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.async_mock import patch +from tests.common import MockConfigEntry + + +async def test_async_setup_no_domain_config(hass: HomeAssistant): + """Test setup without configuration is noop.""" + result = await async_setup_component(hass, DOMAIN, {}) + + assert result is True + + +async def test_async_setup_entry_sets_up_supported_domains(hass: HomeAssistant): + """Test that configuring entry sets up cover domain.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.bond.cover.async_setup_entry" + ) as mock_cover_async_setup_entry: + result = await hass.config_entries.async_setup(config_entry.entry_id) + assert result is True + + await hass.async_block_till_done() + + assert len(mock_cover_async_setup_entry.mock_calls) == 1 + + +async def test_unload_config_entry(hass: HomeAssistant): + """Test that configuration entry supports unloading.""" + config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "1.1.1.1", CONF_ACCESS_TOKEN: "test-token"}, + ) + config_entry.add_to_hass(hass) + with patch( + "homeassistant.components.bond.cover.async_setup_entry", return_value=True + ): + result = await hass.config_entries.async_setup(config_entry.entry_id) + assert result is True + await hass.async_block_till_done() + assert config_entry.entry_id in hass.data[DOMAIN] + assert config_entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.entry_id not in hass.data[DOMAIN] + assert config_entry.state == ENTRY_STATE_NOT_LOADED diff --git a/tests/components/bond/test_utils.py b/tests/components/bond/test_utils.py new file mode 100644 index 00000000000..5e45d1609bf --- /dev/null +++ b/tests/components/bond/test_utils.py @@ -0,0 +1,24 @@ +"""Tests for the Bond cover device.""" +import logging + +from bond import Bond + +from homeassistant.components.bond.utils import get_bond_devices +from homeassistant.core import HomeAssistant + +from tests.async_mock import patch + +_LOGGER = logging.getLogger(__name__) + + +async def test_get_bond_devices(hass: HomeAssistant): + """Tests that the querying for devices delegates to API.""" + bond: Bond = Bond("1.1.1.1", "test-token") + + with patch( + "bond.Bond.getDeviceIds", return_value=["device-1", "device-2"], + ) as mock_get_device_ids, patch("bond.Bond.getDevice") as mock_get_device: + await get_bond_devices(hass, bond) + + assert len(mock_get_device_ids.mock_calls) == 1 + assert len(mock_get_device.mock_calls) == 2