Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 30 additions & 14 deletions homeassistant/components/velbus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
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,
Expand Down Expand Up @@ -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, {})
Expand All @@ -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):
Expand Down Expand Up @@ -176,18 +196,14 @@ async def _on_update(self):
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()),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a material change in the identifier ?

Copy link
Copy Markdown
Member

@frenck frenck Oct 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See also: #58622 (comment)
I needs migration imho

},
"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(),
)
31 changes: 31 additions & 0 deletions tests/components/velbus/conftest.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions tests/components/velbus/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Constants for the Velbus tests."""
PORT_SERIAL = "/dev/ttyACME100"
PORT_TCP = "127.0.1.0.1:3788"
42 changes: 21 additions & 21 deletions tests/components/velbus/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,36 @@
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
from .const import PORT_SERIAL, PORT_TCP

PORT_SERIAL = "/dev/ttyACME100"
PORT_TCP = "127.0.1.0.1:3788"

@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_assert")
def mock_controller_assert():

@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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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"}
Expand Down
56 changes: 56 additions & 0 deletions tests/components/velbus/test_init.py
Original file line number Diff line number Diff line change
@@ -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"