From 089353e949cf2fc921c8dbcb04093ca67cdfce3a Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 8 Nov 2021 17:26:00 +0100 Subject: [PATCH] Use DeviceInfo in velbus (#58622) --- homeassistant/components/velbus/__init__.py | 44 ++++++++++------ tests/components/velbus/conftest.py | 31 ++++++++++++ tests/components/velbus/const.py | 3 ++ tests/components/velbus/test_config_flow.py | 44 ++++++++-------- tests/components/velbus/test_init.py | 56 +++++++++++++++++++++ 5 files changed, 142 insertions(+), 36 deletions(-) create mode 100644 tests/components/velbus/conftest.py create mode 100644 tests/components/velbus/const.py create mode 100644 tests/components/velbus/test_init.py diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index acc90116269..d907557bb01 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -9,8 +9,10 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.device_registry import DeviceEntry +from homeassistant.helpers.entity import DeviceInfo, Entity from .const import ( CONF_INTERFACE, @@ -58,6 +60,22 @@ async def velbus_connect_task( await controller.connect() +def _migrate_device_identifiers(hass: HomeAssistant, entry_id: str) -> None: + """Migrate old device indentifiers.""" + dev_reg = device_registry.async_get(hass) + devices: list[DeviceEntry] = device_registry.async_entries_for_config_entry( + dev_reg, entry_id + ) + for device in devices: + old_identifier = list(next(iter(device.identifiers))) + if len(old_identifier) > 2: + new_identifier = {(old_identifier.pop(0), old_identifier.pop(0))} + _LOGGER.debug( + "migrate identifier '%s' to '%s'", device.identifiers, new_identifier + ) + dev_reg.async_update_device(device.id, new_identifiers=new_identifier) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Establish connection with velbus.""" hass.data.setdefault(DOMAIN, {}) @@ -72,6 +90,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: velbus_connect_task(controller, hass, entry.entry_id) ) + _migrate_device_identifiers(hass, entry.entry_id) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) if hass.services.has_service(DOMAIN, SERVICE_SCAN): @@ -176,18 +196,14 @@ class VelbusEntity(Entity): self.async_write_ha_state() @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return the device info.""" - return { - "identifiers": { - ( - DOMAIN, - self._channel.get_module_address(), - self._channel.get_module_serial(), - ) + return DeviceInfo( + identifiers={ + (DOMAIN, self._channel.get_module_address()), }, - "name": self._channel.get_full_name(), - "manufacturer": "Velleman", - "model": self._channel.get_module_type_name(), - "sw_version": self._channel.get_module_sw_version(), - } + manufacturer="Velleman", + model=self._channel.get_module_type_name(), + name=self._channel.get_full_name(), + sw_version=self._channel.get_module_sw_version(), + ) diff --git a/tests/components/velbus/conftest.py b/tests/components/velbus/conftest.py new file mode 100644 index 00000000000..c13ce3127fa --- /dev/null +++ b/tests/components/velbus/conftest.py @@ -0,0 +1,31 @@ +"""Fixtures for the Velbus tests.""" +from unittest.mock import AsyncMock, patch + +import pytest + +from homeassistant.components.velbus.const import DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME, CONF_PORT +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.velbus.const import PORT_TCP + + +@pytest.fixture(name="controller") +def mock_controller(): + """Mock a successful velbus controller.""" + controller = AsyncMock() + with patch("velbusaio.controller.Velbus", return_value=controller): + yield controller + + +@pytest.fixture(name="config_entry") +def mock_config_entry(hass: HomeAssistant) -> ConfigEntry: + """Create and register mock config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_PORT: PORT_TCP, CONF_NAME: "velbus home"}, + ) + config_entry.add_to_hass(hass) + return config_entry diff --git a/tests/components/velbus/const.py b/tests/components/velbus/const.py new file mode 100644 index 00000000000..374dbce2529 --- /dev/null +++ b/tests/components/velbus/const.py @@ -0,0 +1,3 @@ +"""Constants for the Velbus tests.""" +PORT_SERIAL = "/dev/ttyACME100" +PORT_TCP = "127.0.1.0.1:3788" diff --git a/tests/components/velbus/test_config_flow.py b/tests/components/velbus/test_config_flow.py index 723b6664fd7..6c10b3c84f4 100644 --- a/tests/components/velbus/test_config_flow.py +++ b/tests/components/velbus/test_config_flow.py @@ -7,36 +7,36 @@ from velbusaio.exceptions import VelbusConnectionFailed from homeassistant import data_entry_flow from homeassistant.components.velbus import config_flow from homeassistant.const import CONF_NAME, CONF_PORT +from homeassistant.core import HomeAssistant -from tests.common import MockConfigEntry - -PORT_SERIAL = "/dev/ttyACME100" -PORT_TCP = "127.0.1.0.1:3788" +from .const import PORT_SERIAL, PORT_TCP -@pytest.fixture(name="controller_assert") -def mock_controller_assert(): +@pytest.fixture(autouse=True) +def override_async_setup_entry() -> AsyncMock: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.velbus.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture(name="controller_connection_failed") +def mock_controller_connection_failed(): """Mock the velbus controller with an assert.""" with patch("velbusaio.controller.Velbus", side_effect=VelbusConnectionFailed()): yield -@pytest.fixture(name="controller") -def mock_controller(): - """Mock a successful velbus controller.""" - controller = AsyncMock() - with patch("velbusaio.controller.Velbus", return_value=controller): - yield controller - - -def init_config_flow(hass): +def init_config_flow(hass: HomeAssistant): """Init a configuration flow.""" flow = config_flow.VelbusConfigFlow() flow.hass = hass return flow -async def test_user(hass, controller): +@pytest.mark.usefixtures("controller") +async def test_user(hass: HomeAssistant): """Test user config.""" flow = init_config_flow(hass) @@ -59,7 +59,8 @@ async def test_user(hass, controller): assert result["data"][CONF_PORT] == PORT_TCP -async def test_user_fail(hass, controller_assert): +@pytest.mark.usefixtures("controller_connection_failed") +async def test_user_fail(hass: HomeAssistant): """Test user config.""" flow = init_config_flow(hass) @@ -76,7 +77,8 @@ async def test_user_fail(hass, controller_assert): assert result["errors"] == {CONF_PORT: "cannot_connect"} -async def test_import(hass, controller): +@pytest.mark.usefixtures("controller") +async def test_import(hass: HomeAssistant): """Test import step.""" flow = init_config_flow(hass) @@ -85,12 +87,10 @@ async def test_import(hass, controller): assert result["title"] == "velbus_import" -async def test_abort_if_already_setup(hass): +@pytest.mark.usefixtures("config_entry") +async def test_abort_if_already_setup(hass: HomeAssistant): """Test we abort if Daikin is already setup.""" flow = init_config_flow(hass) - MockConfigEntry( - domain="velbus", data={CONF_PORT: PORT_TCP, CONF_NAME: "velbus home"} - ).add_to_hass(hass) result = await flow.async_step_import( {CONF_PORT: PORT_TCP, CONF_NAME: "velbus import test"} diff --git a/tests/components/velbus/test_init.py b/tests/components/velbus/test_init.py new file mode 100644 index 00000000000..dee00cce16b --- /dev/null +++ b/tests/components/velbus/test_init.py @@ -0,0 +1,56 @@ +"""Tests for the Velbus component initialisation.""" +import pytest + +from homeassistant.components.velbus.const import DOMAIN +from homeassistant.config_entries import ConfigEntry, ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import mock_device_registry + + +@pytest.mark.usefixtures("controller") +async def test_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): + """Test being able to unload an entry.""" + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert config_entry.state is ConfigEntryState.LOADED + + assert await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.NOT_LOADED + assert not hass.data.get(DOMAIN) + + +@pytest.mark.usefixtures("controller") +async def test_device_identifier_migration( + hass: HomeAssistant, config_entry: ConfigEntry +): + """Test being able to unload an entry.""" + original_identifiers = {(DOMAIN, "module_address", "module_serial")} + target_identifiers = {(DOMAIN, "module_address")} + + device_registry = mock_device_registry(hass) + device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers=original_identifiers, + name="channel_name", + manufacturer="Velleman", + model="module_type_name", + sw_version="module_sw_version", + ) + assert device_registry.async_get_device(original_identifiers) + assert not device_registry.async_get_device(target_identifiers) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert not device_registry.async_get_device(original_identifiers) + device_entry = device_registry.async_get_device(target_identifiers) + assert device_entry + assert device_entry.name == "channel_name" + assert device_entry.manufacturer == "Velleman" + assert device_entry.model == "module_type_name" + assert device_entry.sw_version == "module_sw_version"