From 66eeb41e5639f528445798b20e9765fdbe433908 Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:04:07 +0200 Subject: [PATCH] Add product name to title of HomeWizard v2 API migration repair (#155097) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../components/homewizard/__init__.py | 32 +++++++- .../components/homewizard/strings.json | 2 +- tests/components/homewizard/test_init.py | 76 ++++++++++++++++++- 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homewizard/__init__.py b/homeassistant/components/homewizard/__init__.py index 6c9530db72c..fdc571e1dca 100644 --- a/homeassistant/components/homewizard/__init__.py +++ b/homeassistant/components/homewizard/__init__.py @@ -11,6 +11,7 @@ from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.const import CONF_IP_ADDRESS, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue @@ -71,6 +72,25 @@ async def async_unload_entry(hass: HomeAssistant, entry: HomeWizardConfigEntry) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) +def get_main_device( + hass: HomeAssistant, entry: HomeWizardConfigEntry +) -> dr.DeviceEntry | None: + """Helper function to get the main device for the config entry.""" + device_registry = dr.async_get(hass) + device_entries = dr.async_entries_for_config_entry( + device_registry, config_entry_id=entry.entry_id + ) + + if not device_entries: + return None + + # Get first device that is not a sub-device, as this is the main device in HomeWizard + # This is relevant for the P1 Meter which may create sub-devices for external utility meters + return next( + (device for device in device_entries if device.via_device_id is None), None + ) + + async def async_check_v2_support_and_create_issue( hass: HomeAssistant, entry: HomeWizardConfigEntry ) -> None: @@ -79,6 +99,16 @@ async def async_check_v2_support_and_create_issue( if not await has_v2_api(entry.data[CONF_IP_ADDRESS], async_get_clientsession(hass)): return + title = entry.title + + # Try to get the name from the device registry + # This is to make it clearer which device needs reconfiguration, as the config entry title is kept default most of the time + if main_device := get_main_device(hass, entry): + device_name = main_device.name_by_user or main_device.name + + if device_name and entry.title != device_name: + title = f"{entry.title} ({device_name})" + async_create_issue( hass, DOMAIN, @@ -88,7 +118,7 @@ async def async_check_v2_support_and_create_issue( learn_more_url="https://home-assistant.io/integrations/homewizard/#which-button-do-i-need-to-press-to-configure-the-device", translation_key="migrate_to_v2_api", translation_placeholders={ - "title": entry.title, + "title": title, }, severity=IssueSeverity.WARNING, data={"entry_id": entry.entry_id}, diff --git a/homeassistant/components/homewizard/strings.json b/homeassistant/components/homewizard/strings.json index 84594a440f9..84c070fd151 100644 --- a/homeassistant/components/homewizard/strings.json +++ b/homeassistant/components/homewizard/strings.json @@ -177,7 +177,7 @@ }, "issues": { "migrate_to_v2_api": { - "title": "Update authentication method", + "title": "Update the authentication method for {title}", "fix_flow": { "step": { "confirm": { diff --git a/tests/components/homewizard/test_init.py b/tests/components/homewizard/test_init.py index b0562afbb3d..9b8d6e1d2df 100644 --- a/tests/components/homewizard/test_init.py +++ b/tests/components/homewizard/test_init.py @@ -1,16 +1,18 @@ """Tests for the homewizard component.""" from datetime import timedelta -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import weakref from freezegun.api import FrozenDateTimeFactory from homewizard_energy.errors import DisabledError, UnauthorizedError import pytest +from homeassistant.components.homewizard import get_main_device from homeassistant.components.homewizard.const import DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, issue_registry as ir from tests.common import MockConfigEntry, async_fire_time_changed @@ -122,6 +124,78 @@ async def test_load_detect_invalid_token( assert flow["context"].get("entry_id") == mock_config_entry_v2.entry_id +@pytest.mark.usefixtures("mock_homewizardenergy") +async def test_load_creates_repair_issue( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homewizardenergy: MagicMock, +) -> None: + """Test setup creates repair issue for v2 API upgrade.""" + mock_config_entry.add_to_hass(hass) + await hass.async_block_till_done() + + with patch("homeassistant.components.homewizard.has_v2_api", return_value=True): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + await hass.async_block_till_done() + + issue_registry = ir.async_get(hass) + + issue = issue_registry.async_get_issue( + domain=DOMAIN, issue_id=f"migrate_to_v2_api_{mock_config_entry.entry_id}" + ) + assert issue is not None + + # Make sure title placeholder is set correctly + assert issue.translation_placeholders["title"] == "Device" + + +@pytest.mark.usefixtures("mock_homewizardenergy") +async def test_load_creates_repair_issue_when_name_is_updated( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homewizardenergy: MagicMock, +) -> None: + """Test setup creates repair issue for v2 API upgrade and updates title when device name changes.""" + mock_config_entry.add_to_hass(hass) + await hass.async_block_till_done() + + with patch("homeassistant.components.homewizard.has_v2_api", return_value=True): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + await hass.async_block_till_done() + + issue_registry = ir.async_get(hass) + issue_id = f"migrate_to_v2_api_{mock_config_entry.entry_id}" + + issue = issue_registry.async_get_issue(domain=DOMAIN, issue_id=issue_id) + assert issue is not None + + # Initial title should be "Device" + assert issue.translation_placeholders["title"] == "Device" + + # Update the device name + device_registry = dr.async_get(hass) + device = get_main_device(hass, mock_config_entry) + + # Update device name + device_registry.async_update_device( + device_id=device.id, + name_by_user="My HomeWizard Device", + ) + + # Reload integration to trigger issue update + with patch("homeassistant.components.homewizard.has_v2_api", return_value=True): + await hass.config_entries.async_reload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + issue = issue_registry.async_get_issue(domain=DOMAIN, issue_id=issue_id) + assert issue is not None + + # Title should now reflect updated device name + assert issue.translation_placeholders["title"] == "Device (My HomeWizard Device)" + + @pytest.mark.usefixtures("mock_homewizardenergy") async def test_load_removes_reauth_flow( hass: HomeAssistant,