diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index 618268206b0..1c4ba99d1a4 100755 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -120,16 +120,11 @@ async def async_setup_entry(hass, entry): """Set up a bridge from a config entry.""" LOGGER.debug("Setting up entry %s", entry.data) bridge = DynaliteBridge(hass, entry.data) - hass.data[DOMAIN][entry.entry_id] = bridge entry.add_update_listener(async_entry_changed) if not await bridge.async_setup(): LOGGER.error("Could not set up bridge for entry %s", entry.data) - hass.data[DOMAIN].pop(entry.entry_id) - return False - if not await bridge.try_connection(): - LOGGER.error("Could not connect with entry %s", entry) - hass.data[DOMAIN].pop(entry.entry_id) raise ConfigEntryNotReady + hass.data[DOMAIN][entry.entry_id] = bridge hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, "light") ) diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index f2ffe447d6c..85a187249df 100755 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -1,16 +1,11 @@ """Code to handle a Dynalite bridge.""" -import asyncio - -from dynalite_devices_lib import DynaliteDevices +from dynalite_devices_lib.dynalite_devices import DynaliteDevices from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import CONF_ALL, CONF_HOST, LOGGER - -CONNECT_TIMEOUT = 30 -CONNECT_INTERVAL = 1 +from .const import CONF_ALL, CONF_HOST, ENTITY_PLATFORMS, LOGGER class DynaliteBridge: @@ -20,8 +15,8 @@ class DynaliteBridge: """Initialize the system based on host parameter.""" self.hass = hass self.area = {} - self.async_add_devices = None - self.waiting_devices = [] + self.async_add_devices = {} + self.waiting_devices = {} self.host = config[CONF_HOST] # Configure the dynalite devices self.dynalite_devices = DynaliteDevices( @@ -38,7 +33,7 @@ class DynaliteBridge: async def reload_config(self, config): """Reconfigure a bridge when config changes.""" - LOGGER.debug("Setting up bridge - host %s, config %s", self.host, config) + LOGGER.debug("Reloading bridge - host %s, config %s", self.host, config) self.dynalite_devices.configure(config) def update_signal(self, device=None): @@ -62,27 +57,22 @@ class DynaliteBridge: else: async_dispatcher_send(self.hass, self.update_signal(device)) - async def try_connection(self): - """Try to connect to dynalite with timeout.""" - # Currently by polling. Future - will need to change the library to be proactive - for _ in range(0, CONNECT_TIMEOUT): - if self.dynalite_devices.available: - return True - await asyncio.sleep(CONNECT_INTERVAL) - return False - @callback - def register_add_devices(self, async_add_devices): + def register_add_devices(self, platform, async_add_devices): """Add an async_add_entities for a category.""" - self.async_add_devices = async_add_devices - if self.waiting_devices: - self.async_add_devices(self.waiting_devices) + self.async_add_devices[platform] = async_add_devices + if platform in self.waiting_devices: + self.async_add_devices[platform](self.waiting_devices[platform]) def add_devices_when_registered(self, devices): """Add the devices to HA if the add devices callback was registered, otherwise queue until it is.""" - if not devices: - return - if self.async_add_devices: - self.async_add_devices(devices) - else: # handle it later when it is registered - self.waiting_devices.extend(devices) + for platform in ENTITY_PLATFORMS: + platform_devices = [ + device for device in devices if device.category == platform + ] + if platform in self.async_add_devices: + self.async_add_devices[platform](platform_devices) + else: # handle it later when it is registered + if platform not in self.waiting_devices: + self.waiting_devices[platform] = [] + self.waiting_devices[platform].extend(platform_devices) diff --git a/homeassistant/components/dynalite/config_flow.py b/homeassistant/components/dynalite/config_flow.py index c5508bc8db2..10d66c82d52 100755 --- a/homeassistant/components/dynalite/config_flow.py +++ b/homeassistant/components/dynalite/config_flow.py @@ -31,8 +31,6 @@ class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): bridge = DynaliteBridge(self.hass, import_info) if not await bridge.async_setup(): LOGGER.error("Unable to setup bridge - import info=%s", import_info) - return self.async_abort(reason="bridge_setup_failed") - if not await bridge.try_connection(): return self.async_abort(reason="no_connection") LOGGER.debug("Creating entry for the bridge - %s", import_info) return self.async_create_entry(title=host, data=import_info) diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index f7795554465..2e86d49c825 100755 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -4,6 +4,8 @@ import logging LOGGER = logging.getLogger(__package__) DOMAIN = "dynalite" +ENTITY_PLATFORMS = ["light"] + CONF_ACTIVE = "active" CONF_ALL = "ALL" CONF_AREA = "area" @@ -17,5 +19,6 @@ CONF_NAME = "name" CONF_POLLTIMER = "polltimer" CONF_PORT = "port" + DEFAULT_NAME = "dynalite" DEFAULT_PORT = 12345 diff --git a/homeassistant/components/dynalite/dynalitebase.py b/homeassistant/components/dynalite/dynalitebase.py new file mode 100755 index 00000000000..8bb1ab2dc42 --- /dev/null +++ b/homeassistant/components/dynalite/dynalitebase.py @@ -0,0 +1,85 @@ +"""Support for the Dynalite devices as entities.""" +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN, LOGGER + + +def async_setup_entry_base( + hass, config_entry, async_add_entities, platform, entity_from_device +): + """Record the async_add_entities function to add them later when received from Dynalite.""" + LOGGER.debug("Setting up %s entry = %s", platform, config_entry.data) + bridge = hass.data[DOMAIN][config_entry.entry_id] + + @callback + def async_add_entities_platform(devices): + # assumes it is called with a single platform + added_entities = [] + for device in devices: + if device.category == platform: + added_entities.append(entity_from_device(device, bridge)) + if added_entities: + async_add_entities(added_entities) + + bridge.register_add_devices(platform, async_add_entities_platform) + + +class DynaliteBase(Entity): + """Base class for the Dynalite entities.""" + + def __init__(self, device, bridge): + """Initialize the base class.""" + self._device = device + self._bridge = bridge + self._unsub_dispatchers = [] + + @property + def name(self): + """Return the name of the entity.""" + return self._device.name + + @property + def unique_id(self): + """Return the unique ID of the entity.""" + return self._device.unique_id + + @property + def available(self): + """Return if entity is available.""" + return self._device.available + + @property + def device_info(self): + """Device info for this entity.""" + return { + "identifiers": {(DOMAIN, self._device.unique_id)}, + "name": self.name, + "manufacturer": "Dynalite", + } + + async def async_added_to_hass(self): + """Added to hass so need to register to dispatch.""" + # register for device specific update + self._unsub_dispatchers.append( + async_dispatcher_connect( + self.hass, + self._bridge.update_signal(self._device), + self.async_schedule_update_ha_state, + ) + ) + # register for wide update + self._unsub_dispatchers.append( + async_dispatcher_connect( + self.hass, + self._bridge.update_signal(), + self.async_schedule_update_ha_state, + ) + ) + + async def async_will_remove_from_hass(self): + """Unregister signal dispatch listeners when being removed.""" + for unsub in self._unsub_dispatchers: + unsub() + self._unsub_dispatchers = [] diff --git a/homeassistant/components/dynalite/light.py b/homeassistant/components/dynalite/light.py index 652a6178705..caa39ad573a 100755 --- a/homeassistant/components/dynalite/light.py +++ b/homeassistant/components/dynalite/light.py @@ -1,64 +1,25 @@ """Support for Dynalite channels as lights.""" from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from .const import DOMAIN, LOGGER +from .dynalitebase import DynaliteBase, async_setup_entry_base async def async_setup_entry(hass, config_entry, async_add_entities): """Record the async_add_entities function to add them later when received from Dynalite.""" - LOGGER.debug("Setting up light entry = %s", config_entry.data) - bridge = hass.data[DOMAIN][config_entry.entry_id] @callback - def async_add_lights(devices): - added_lights = [] - for device in devices: - if device.category == "light": - added_lights.append(DynaliteLight(device, bridge)) - if added_lights: - async_add_entities(added_lights) + def light_from_device(device, bridge): + return DynaliteLight(device, bridge) - bridge.register_add_devices(async_add_lights) + async_setup_entry_base( + hass, config_entry, async_add_entities, "light", light_from_device + ) -class DynaliteLight(Light): +class DynaliteLight(DynaliteBase, Light): """Representation of a Dynalite Channel as a Home Assistant Light.""" - def __init__(self, device, bridge): - """Initialize the base class.""" - self._device = device - self._bridge = bridge - - @property - def name(self): - """Return the name of the entity.""" - return self._device.name - - @property - def unique_id(self): - """Return the unique ID of the entity.""" - return self._device.unique_id - - @property - def available(self): - """Return if entity is available.""" - return self._device.available - - async def async_update(self): - """Update the entity.""" - return - - @property - def device_info(self): - """Device info for this entity.""" - return { - "identifiers": {(DOMAIN, self.unique_id)}, - "name": self.name, - "manufacturer": "Dynalite", - } - @property def brightness(self): """Return the brightness of this light between 0..255.""" @@ -81,16 +42,3 @@ class DynaliteLight(Light): def supported_features(self): """Flag supported features.""" return SUPPORT_BRIGHTNESS - - async def async_added_to_hass(self): - """Added to hass so need to register to dispatch.""" - # register for device specific update - async_dispatcher_connect( - self.hass, - self._bridge.update_signal(self._device), - self.async_schedule_update_ha_state, - ) - # register for wide update - async_dispatcher_connect( - self.hass, self._bridge.update_signal(), self.async_schedule_update_ha_state - ) diff --git a/homeassistant/components/dynalite/manifest.json b/homeassistant/components/dynalite/manifest.json index fc552ea6ad1..18f1ebed919 100755 --- a/homeassistant/components/dynalite/manifest.json +++ b/homeassistant/components/dynalite/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/dynalite", "dependencies": [], "codeowners": ["@ziv1234"], - "requirements": ["dynalite_devices==0.1.26"] + "requirements": ["dynalite_devices==0.1.30"] } diff --git a/requirements_all.txt b/requirements_all.txt index 9d42de37cda..b17cfce1ff4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -463,7 +463,7 @@ dsmr_parser==0.18 dweepy==0.3.0 # homeassistant.components.dynalite -dynalite_devices==0.1.26 +dynalite_devices==0.1.30 # homeassistant.components.rainforest_eagle eagle200_reader==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 59b51e13062..de43f8f9d21 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -171,7 +171,7 @@ distro==1.4.0 dsmr_parser==0.18 # homeassistant.components.dynalite -dynalite_devices==0.1.26 +dynalite_devices==0.1.30 # homeassistant.components.ee_brightbox eebrightbox==0.0.4 diff --git a/tests/components/dynalite/common.py b/tests/components/dynalite/common.py index 3bdf3a60dd7..97750140811 100755 --- a/tests/components/dynalite/common.py +++ b/tests/components/dynalite/common.py @@ -1,9 +1,64 @@ -"""Common functions for the Dynalite tests.""" +"""Common functions for tests.""" +from asynctest import CoroutineMock, Mock, call, patch from homeassistant.components import dynalite +from homeassistant.helpers import entity_registry + +from tests.common import MockConfigEntry + +ATTR_SERVICE = "service" +ATTR_METHOD = "method" +ATTR_ARGS = "args" -def get_bridge_from_hass(hass_obj): - """Get the bridge from hass.data.""" - key = next(iter(hass_obj.data[dynalite.DOMAIN])) - return hass_obj.data[dynalite.DOMAIN][key] +def create_mock_device(platform, spec): + """Create a dynalite mock device for a platform according to a spec.""" + device = Mock(spec=spec) + device.category = platform + device.unique_id = "UNIQUE" + device.name = "NAME" + device.device_class = "Device Class" + return device + + +async def get_entry_id_from_hass(hass): + """Get the config entry id from hass.""" + ent_reg = await entity_registry.async_get_registry(hass) + assert ent_reg + conf_entries = hass.config_entries.async_entries(dynalite.DOMAIN) + assert len(conf_entries) == 1 + return conf_entries[0].entry_id + + +async def create_entity_from_device(hass, device): + """Set up the component and platform and create a light based on the device provided.""" + host = "1.2.3.4" + entry = MockConfigEntry(domain=dynalite.DOMAIN, data={dynalite.CONF_HOST: host}) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.dynalite.bridge.DynaliteDevices" + ) as mock_dyn_dev: + mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + new_device_func = mock_dyn_dev.mock_calls[1][2]["newDeviceFunc"] + new_device_func([device]) + await hass.async_block_till_done() + + +async def run_service_tests(hass, device, platform, services): + """Run a series of service calls and check that the entity and device behave correctly.""" + for cur_item in services: + service = cur_item[ATTR_SERVICE] + args = cur_item.get(ATTR_ARGS, {}) + service_data = {"entity_id": f"{platform}.name", **args} + await hass.services.async_call(platform, service, service_data, blocking=True) + await hass.async_block_till_done() + for check_item in services: + check_method = getattr(device, check_item[ATTR_METHOD]) + if check_item[ATTR_SERVICE] == service: + check_method.assert_called_once() + assert check_method.mock_calls == [call(**args)] + check_method.reset_mock() + else: + check_method.assert_not_called() diff --git a/tests/components/dynalite/test_bridge.py b/tests/components/dynalite/test_bridge.py index 7b3a8312402..0c9ea517992 100755 --- a/tests/components/dynalite/test_bridge.py +++ b/tests/components/dynalite/test_bridge.py @@ -1,81 +1,85 @@ """Test Dynalite bridge.""" -from unittest.mock import Mock, call -from asynctest import patch -from dynalite_devices_lib import CONF_ALL -import pytest +from asynctest import CoroutineMock, Mock, patch +from dynalite_devices_lib.const import CONF_ALL from homeassistant.components import dynalite +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from tests.common import MockConfigEntry -@pytest.fixture -def dyn_bridge(): - """Define a basic mock bridge.""" - hass = Mock() +async def test_update_device(hass): + """Test that update works.""" host = "1.2.3.4" - bridge = dynalite.DynaliteBridge(hass, {dynalite.CONF_HOST: host}) - return bridge - - -async def test_update_device(dyn_bridge): - """Test a successful setup.""" - async_dispatch = Mock() - + entry = MockConfigEntry(domain=dynalite.DOMAIN, data={dynalite.CONF_HOST: host}) + entry.add_to_hass(hass) with patch( - "homeassistant.components.dynalite.bridge.async_dispatcher_send", async_dispatch - ): - dyn_bridge.update_device(CONF_ALL) - async_dispatch.assert_called_once() - assert async_dispatch.mock_calls[0] == call( - dyn_bridge.hass, f"dynalite-update-{dyn_bridge.host}" - ) - async_dispatch.reset_mock() - device = Mock - device.unique_id = "abcdef" - dyn_bridge.update_device(device) - async_dispatch.assert_called_once() - assert async_dispatch.mock_calls[0] == call( - dyn_bridge.hass, f"dynalite-update-{dyn_bridge.host}-{device.unique_id}" - ) + "homeassistant.components.dynalite.bridge.DynaliteDevices" + ) as mock_dyn_dev: + mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + assert await hass.config_entries.async_setup(entry.entry_id) + # Not waiting so it add the devices before registration + update_device_func = mock_dyn_dev.mock_calls[1][2]["updateDeviceFunc"] + device = Mock() + device.unique_id = "abcdef" + wide_func = Mock() + async_dispatcher_connect(hass, f"dynalite-update-{host}", wide_func) + specific_func = Mock() + async_dispatcher_connect( + hass, f"dynalite-update-{host}-{device.unique_id}", specific_func + ) + update_device_func(CONF_ALL) + await hass.async_block_till_done() + wide_func.assert_called_once() + specific_func.assert_not_called() + update_device_func(device) + await hass.async_block_till_done() + wide_func.assert_called_once() + specific_func.assert_called_once() -async def test_add_devices_then_register(dyn_bridge): +async def test_add_devices_then_register(hass): """Test that add_devices work.""" - # First test empty - dyn_bridge.add_devices_when_registered([]) - assert not dyn_bridge.waiting_devices + host = "1.2.3.4" + entry = MockConfigEntry(domain=dynalite.DOMAIN, data={dynalite.CONF_HOST: host}) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.dynalite.bridge.DynaliteDevices" + ) as mock_dyn_dev: + mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + assert await hass.config_entries.async_setup(entry.entry_id) + # Not waiting so it add the devices before registration + new_device_func = mock_dyn_dev.mock_calls[1][2]["newDeviceFunc"] # Now with devices device1 = Mock() device1.category = "light" + device1.name = "NAME" device2 = Mock() device2.category = "switch" - dyn_bridge.add_devices_when_registered([device1, device2]) - reg_func = Mock() - dyn_bridge.register_add_devices(reg_func) - reg_func.assert_called_once() - assert reg_func.mock_calls[0][1][0][0] is device1 + new_device_func([device1, device2]) + await hass.async_block_till_done() + assert hass.states.get("light.name") -async def test_register_then_add_devices(dyn_bridge): +async def test_register_then_add_devices(hass): """Test that add_devices work after register_add_entities.""" + host = "1.2.3.4" + entry = MockConfigEntry(domain=dynalite.DOMAIN, data={dynalite.CONF_HOST: host}) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.dynalite.bridge.DynaliteDevices" + ) as mock_dyn_dev: + mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + new_device_func = mock_dyn_dev.mock_calls[1][2]["newDeviceFunc"] + # Now with devices device1 = Mock() device1.category = "light" + device1.name = "NAME" device2 = Mock() device2.category = "switch" - reg_func = Mock() - dyn_bridge.register_add_devices(reg_func) - dyn_bridge.add_devices_when_registered([device1, device2]) - reg_func.assert_called_once() - assert reg_func.mock_calls[0][1][0][0] is device1 - - -async def test_try_connection(dyn_bridge): - """Test that try connection works.""" - # successful - with patch.object(dyn_bridge.dynalite_devices, "connected", True): - assert await dyn_bridge.try_connection() - # unsuccessful - with patch.object(dyn_bridge.dynalite_devices, "connected", False), patch( - "homeassistant.components.dynalite.bridge.CONNECT_INTERVAL", 0 - ): - assert not await dyn_bridge.try_connection() + new_device_func([device1, device2]) + await hass.async_block_till_done() + assert hass.states.get("light.name") diff --git a/tests/components/dynalite/test_config_flow.py b/tests/components/dynalite/test_config_flow.py index fb8530aec1e..96e361e260f 100755 --- a/tests/components/dynalite/test_config_flow.py +++ b/tests/components/dynalite/test_config_flow.py @@ -1,53 +1,50 @@ """Test Dynalite config flow.""" -from asynctest import patch + +from asynctest import CoroutineMock, patch from homeassistant import config_entries from homeassistant.components import dynalite -from .common import get_bridge_from_hass - from tests.common import MockConfigEntry -async def run_flow(hass, setup, connection): +async def run_flow(hass, connection): """Run a flow with or without errors and return result.""" host = "1.2.3.4" with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", - return_value=setup, - ), patch( - "homeassistant.components.dynalite.bridge.DynaliteDevices.available", connection - ), patch( - "homeassistant.components.dynalite.bridge.CONNECT_INTERVAL", 0 + side_effect=connection, ): result = await hass.config_entries.flow.async_init( dynalite.DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={dynalite.CONF_HOST: host}, ) + await hass.async_block_till_done() return result async def test_flow_works(hass): """Test a successful config flow.""" - result = await run_flow(hass, True, True) + result = await run_flow(hass, [True, True]) assert result["type"] == "create_entry" + assert result["result"].state == "loaded" async def test_flow_setup_fails(hass): """Test a flow where async_setup fails.""" - result = await run_flow(hass, False, True) - assert result["type"] == "abort" - assert result["reason"] == "bridge_setup_failed" - - -async def test_flow_no_connection(hass): - """Test a flow where connection times out.""" - result = await run_flow(hass, True, False) + result = await run_flow(hass, [False]) assert result["type"] == "abort" assert result["reason"] == "no_connection" +async def test_flow_setup_fails_in_setup_entry(hass): + """Test a flow where the initial check works but inside setup_entry, the bridge setup fails.""" + result = await run_flow(hass, [True, False]) + assert result["type"] == "create_entry" + assert result["result"].state == "setup_retry" + + async def test_existing(hass): """Test when the entry exists with the same config.""" host = "1.2.3.4" @@ -57,8 +54,6 @@ async def test_existing(hass): with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", return_value=True, - ), patch( - "homeassistant.components.dynalite.bridge.DynaliteDevices.available", True ): result = await hass.config_entries.flow.async_init( dynalite.DOMAIN, @@ -70,31 +65,30 @@ async def test_existing(hass): async def test_existing_update(hass): - """Test when the entry exists with the same config.""" + """Test when the entry exists with a different config.""" host = "1.2.3.4" port1 = 7777 port2 = 8888 + entry = MockConfigEntry( + domain=dynalite.DOMAIN, + data={dynalite.CONF_HOST: host, dynalite.CONF_PORT: port1}, + ) + entry.add_to_hass(hass) with patch( - "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", - return_value=True, - ), patch( - "homeassistant.components.dynalite.bridge.DynaliteDevices.available", True - ): - assert await hass.config_entries.flow.async_init( - dynalite.DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data={dynalite.CONF_HOST: host, dynalite.CONF_PORT: port1}, - ) + "homeassistant.components.dynalite.bridge.DynaliteDevices" + ) as mock_dyn_dev: + mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - old_bridge = get_bridge_from_hass(hass) - assert old_bridge.dynalite_devices.port == port1 + mock_dyn_dev().configure.assert_called_once() + assert mock_dyn_dev().configure.mock_calls[0][1][0][dynalite.CONF_PORT] == port1 result = await hass.config_entries.flow.async_init( dynalite.DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={dynalite.CONF_HOST: host, dynalite.CONF_PORT: port2}, ) await hass.async_block_till_done() + assert mock_dyn_dev().configure.call_count == 2 + assert mock_dyn_dev().configure.mock_calls[1][1][0][dynalite.CONF_PORT] == port2 assert result["type"] == "abort" assert result["reason"] == "already_configured" - bridge = get_bridge_from_hass(hass) - assert bridge.dynalite_devices.port == port2 diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index d8ef0d7d259..6c9309cb4e5 100755 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -1,6 +1,7 @@ """Test Dynalite __init__.""" -from asynctest import patch + +from asynctest import call, patch from homeassistant.components import dynalite from homeassistant.setup import async_setup_component @@ -12,51 +13,75 @@ async def test_empty_config(hass): """Test with an empty config.""" assert await async_setup_component(hass, dynalite.DOMAIN, {}) is True assert len(hass.config_entries.flow.async_progress()) == 0 - assert hass.data[dynalite.DOMAIN] == {} + assert len(hass.config_entries.async_entries(dynalite.DOMAIN)) == 0 async def test_async_setup(hass): """Test a successful setup.""" host = "1.2.3.4" with patch( - "dynalite_devices_lib.DynaliteDevices.async_setup", return_value=True - ), patch("dynalite_devices_lib.DynaliteDevices.available", True): + "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", + return_value=True, + ): assert await async_setup_component( hass, dynalite.DOMAIN, - {dynalite.DOMAIN: {dynalite.CONF_BRIDGES: [{dynalite.CONF_HOST: host}]}}, + { + dynalite.DOMAIN: { + dynalite.CONF_BRIDGES: [ + { + dynalite.CONF_HOST: host, + dynalite.CONF_AREA: {"1": {dynalite.CONF_NAME: "Name"}}, + } + ] + } + }, ) - - assert len(hass.data[dynalite.DOMAIN]) == 1 + await hass.async_block_till_done() + assert len(hass.config_entries.async_entries(dynalite.DOMAIN)) == 1 -async def test_async_setup_failed(hass): - """Test a setup when DynaliteBridge.async_setup fails.""" +async def test_async_setup_bad_config2(hass): + """Test a successful with bad config on numbers.""" host = "1.2.3.4" - with patch("dynalite_devices_lib.DynaliteDevices.async_setup", return_value=False): - assert await async_setup_component( + with patch( + "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", + return_value=True, + ): + assert not await async_setup_component( hass, dynalite.DOMAIN, - {dynalite.DOMAIN: {dynalite.CONF_BRIDGES: [{dynalite.CONF_HOST: host}]}}, + { + dynalite.DOMAIN: { + dynalite.CONF_BRIDGES: [ + { + dynalite.CONF_HOST: host, + dynalite.CONF_AREA: {"WRONG": {dynalite.CONF_NAME: "Name"}}, + } + ] + } + }, ) - assert hass.data[dynalite.DOMAIN] == {} + await hass.async_block_till_done() + assert len(hass.config_entries.async_entries(dynalite.DOMAIN)) == 0 async def test_unload_entry(hass): """Test being able to unload an entry.""" host = "1.2.3.4" - entry = MockConfigEntry(domain=dynalite.DOMAIN, data={"host": host}) + entry = MockConfigEntry(domain=dynalite.DOMAIN, data={dynalite.CONF_HOST: host}) entry.add_to_hass(hass) - with patch( - "dynalite_devices_lib.DynaliteDevices.async_setup", return_value=True - ), patch("dynalite_devices_lib.DynaliteDevices.available", True): - assert await async_setup_component( - hass, - dynalite.DOMAIN, - {dynalite.DOMAIN: {dynalite.CONF_BRIDGES: [{dynalite.CONF_HOST: host}]}}, - ) - assert hass.data[dynalite.DOMAIN].get(entry.entry_id) - - assert await hass.config_entries.async_unload(entry.entry_id) - assert not hass.data[dynalite.DOMAIN].get(entry.entry_id) + "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", + return_value=True, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert len(hass.config_entries.async_entries(dynalite.DOMAIN)) == 1 + with patch.object( + hass.config_entries, "async_forward_entry_unload", return_value=True + ) as mock_unload: + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + mock_unload.assert_called_once() + assert mock_unload.mock_calls == [call(entry, "light")] diff --git a/tests/components/dynalite/test_light.py b/tests/components/dynalite/test_light.py index 9934bac8720..deea32d2e34 100755 --- a/tests/components/dynalite/test_light.py +++ b/tests/components/dynalite/test_light.py @@ -1,78 +1,49 @@ """Test Dynalite light.""" -from unittest.mock import Mock -from asynctest import CoroutineMock, patch +from dynalite_devices_lib.light import DynaliteChannelLightDevice import pytest -from homeassistant.components import dynalite from homeassistant.components.light import SUPPORT_BRIGHTNESS -from homeassistant.setup import async_setup_component + +from .common import ( + ATTR_METHOD, + ATTR_SERVICE, + create_entity_from_device, + create_mock_device, + get_entry_id_from_hass, + run_service_tests, +) @pytest.fixture def mock_device(): """Mock a Dynalite device.""" - device = Mock() - device.category = "light" - device.unique_id = "UNIQUE" - device.name = "NAME" - device.device_info = { - "identifiers": {(dynalite.DOMAIN, device.unique_id)}, - "name": device.name, - "manufacturer": "Dynalite", - } - return device - - -async def create_light_from_device(hass, device): - """Set up the component and platform and create a light based on the device provided.""" - host = "1.2.3.4" - with patch( - "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", - return_value=True, - ), patch( - "homeassistant.components.dynalite.bridge.DynaliteDevices.available", True - ): - assert await async_setup_component( - hass, - dynalite.DOMAIN, - {dynalite.DOMAIN: {dynalite.CONF_BRIDGES: [{dynalite.CONF_HOST: host}]}}, - ) - await hass.async_block_till_done() - # Find the bridge - bridge = None - assert len(hass.data[dynalite.DOMAIN]) == 1 - key = next(iter(hass.data[dynalite.DOMAIN])) - bridge = hass.data[dynalite.DOMAIN][key] - bridge.dynalite_devices.newDeviceFunc([device]) - await hass.async_block_till_done() + return create_mock_device("light", DynaliteChannelLightDevice) async def test_light_setup(hass, mock_device): """Test a successful setup.""" - await create_light_from_device(hass, mock_device) + await create_entity_from_device(hass, mock_device) entity_state = hass.states.get("light.name") + assert entity_state.attributes["friendly_name"] == mock_device.name assert entity_state.attributes["brightness"] == mock_device.brightness assert entity_state.attributes["supported_features"] == SUPPORT_BRIGHTNESS - - -async def test_turn_on(hass, mock_device): - """Test turning a light on.""" - mock_device.async_turn_on = CoroutineMock(return_value=True) - await create_light_from_device(hass, mock_device) - await hass.services.async_call( - "light", "turn_on", {"entity_id": "light.name"}, blocking=True + await run_service_tests( + hass, + mock_device, + "light", + [ + {ATTR_SERVICE: "turn_on", ATTR_METHOD: "async_turn_on"}, + {ATTR_SERVICE: "turn_off", ATTR_METHOD: "async_turn_off"}, + ], ) - await hass.async_block_till_done() - mock_device.async_turn_on.assert_awaited_once() -async def test_turn_off(hass, mock_device): - """Test turning a light off.""" - mock_device.async_turn_off = CoroutineMock(return_value=True) - await create_light_from_device(hass, mock_device) - await hass.services.async_call( - "light", "turn_off", {"entity_id": "light.name"}, blocking=True - ) +async def test_remove_entity(hass, mock_device): + """Test when an entity is removed from HA.""" + await create_entity_from_device(hass, mock_device) + assert hass.states.get("light.name") + entry_id = await get_entry_id_from_hass(hass) + assert await hass.config_entries.async_unload(entry_id) await hass.async_block_till_done() - mock_device.async_turn_off.assert_awaited_once() + assert not hass.states.get("light.name")