From 74d3b2374be106c83e0ce745ecd682af5aa70031 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Tue, 7 Mar 2023 20:24:08 -0500 Subject: [PATCH] Clean ZHA radio path with trailing whitespace (#89299) * Clean config flow entries with trailing whitespace * Rewrite the config entry at runtime, without upgrading * Skip intermediate `data = config_entry.data` variable * Perform a deepcopy to ensure the config entry will actually be updated --- homeassistant/components/zha/__init__.py | 10 ++++++ tests/components/zha/test_init.py | 41 +++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index d32dcf0bda6..d0496fe7b60 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -1,5 +1,6 @@ """Support for Zigbee Home Automation devices.""" import asyncio +import copy import logging import os @@ -90,6 +91,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b Will automatically load components to support devices found on the network. """ + # Strip whitespace around `socket://` URIs, this is no longer accepted by zigpy + # This will be removed in 2023.7.0 + path = config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH] + data = copy.deepcopy(dict(config_entry.data)) + + if path.startswith("socket://") and path != path.strip(): + data[CONF_DEVICE][CONF_DEVICE_PATH] = path.strip() + hass.config_entries.async_update_entry(config_entry, data=data) + zha_data = hass.data.setdefault(DATA_ZHA, {}) config = zha_data.get(DATA_ZHA_CONFIG, {}) diff --git a/tests/components/zha/test_init.py b/tests/components/zha/test_init.py index e580242a677..a92631f6da3 100644 --- a/tests/components/zha/test_init.py +++ b/tests/components/zha/test_init.py @@ -1,9 +1,10 @@ """Tests for ZHA integration init.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock, Mock, patch import pytest from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH +from homeassistant.components.zha import async_setup_entry from homeassistant.components.zha.core.const import ( CONF_BAUDRATE, CONF_RADIO_TYPE, @@ -108,3 +109,41 @@ async def test_config_depreciation(hass: HomeAssistant, zha_config) -> None: ) as setup_mock: assert await async_setup_component(hass, DOMAIN, {DOMAIN: zha_config}) assert setup_mock.call_count == 1 + + +@pytest.mark.parametrize( + ("path", "cleaned_path"), + [ + ("/dev/path1", "/dev/path1"), + ("/dev/path1 ", "/dev/path1 "), + ("socket://dev/path1 ", "socket://dev/path1"), + ], +) +@patch("homeassistant.components.zha.setup_quirks", Mock(return_value=True)) +@patch("homeassistant.components.zha.api.async_load_api", Mock(return_value=True)) +async def test_setup_with_v3_spaces_in_uri( + hass: HomeAssistant, path: str, cleaned_path: str +) -> None: + """Test migration of config entry from v3 with spaces after `socket://` URI.""" + config_entry_v3 = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_RADIO_TYPE: DATA_RADIO_TYPE, + CONF_DEVICE: {CONF_DEVICE_PATH: path, CONF_BAUDRATE: 115200}, + }, + version=3, + ) + config_entry_v3.add_to_hass(hass) + + with patch( + "homeassistant.components.zha.ZHAGateway", return_value=AsyncMock() + ) as mock_gateway: + mock_gateway.return_value.coordinator_ieee = "mock_ieee" + mock_gateway.return_value.radio_description = "mock_radio" + + assert await async_setup_entry(hass, config_entry_v3) + hass.data[DOMAIN]["zha_gateway"] = mock_gateway.return_value + + assert config_entry_v3.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE + assert config_entry_v3.data[CONF_DEVICE][CONF_DEVICE_PATH] == cleaned_path + assert config_entry_v3.version == 3