From bd77abd265c4abc01565d12b8837b88a0b69dfa9 Mon Sep 17 00:00:00 2001 From: rrooggiieerr Date: Sun, 4 Oct 2026 15:27:57 +0200 Subject: [PATCH] Handle unsupported devices for the my-PV integration (#183920) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- homeassistant/components/my_pv/__init__.py | 13 +++- homeassistant/components/my_pv/config_flow.py | 6 +- homeassistant/components/my_pv/strings.json | 6 +- tests/components/my_pv/test_config_flow.py | 63 ++++++++++++++++++- tests/components/my_pv/test_init.py | 23 ++++++- 5 files changed, 105 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/my_pv/__init__.py b/homeassistant/components/my_pv/__init__.py index 56826d0310e1..60172aa0581b 100644 --- a/homeassistant/components/my_pv/__init__.py +++ b/homeassistant/components/my_pv/__init__.py @@ -1,11 +1,15 @@ """The my-PV integration for Home Assistant.""" from my_pv import MyPVLocalDevice -from my_pv.exceptions import MyPVAuthenticationError +from my_pv.exceptions import MyPVAuthenticationError, MyPVDeviceNotSupportedError from homeassistant.const import CONF_HOST, CONF_PASSWORD, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.exceptions import ( + ConfigEntryAuthFailed, + ConfigEntryError, + ConfigEntryNotReady, +) from .const import DOMAIN from .coordinator import MyPVConfigEntry, MyPVCoordinator @@ -36,6 +40,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: MyPVConfigEntry) -> bool translation_domain=DOMAIN, translation_key="auth_error", ) from exc + except MyPVDeviceNotSupportedError as exc: + raise ConfigEntryError( + translation_domain=DOMAIN, + translation_key="unsupported_device", + ) from exc coordinator = MyPVCoordinator(hass, entry, device) diff --git a/homeassistant/components/my_pv/config_flow.py b/homeassistant/components/my_pv/config_flow.py index 966bfdff2ff1..d7642dc57f09 100644 --- a/homeassistant/components/my_pv/config_flow.py +++ b/homeassistant/components/my_pv/config_flow.py @@ -5,7 +5,7 @@ import logging from typing import Any, Final, override from my_pv import MyPVLocalDevice -from my_pv.exceptions import MyPVAuthenticationError +from my_pv.exceptions import MyPVAuthenticationError, MyPVDeviceNotSupportedError import probatio from homeassistant.config_entries import ConfigFlow, ConfigFlowResult @@ -93,6 +93,8 @@ class MyPVConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="cannot_connect") except MyPVAuthenticationError: password_needed = True + except MyPVDeviceNotSupportedError: + return self.async_abort(reason="unsupported_device") finally: await device.disconnect() @@ -142,6 +144,8 @@ class MyPVConfigFlow(ConfigFlow, domain=DOMAIN): errors[CONF_BASE] = "cannot_connect" except MyPVAuthenticationError: password_needed = True + except MyPVDeviceNotSupportedError: + return self.async_abort(reason="unsupported_device") finally: await device.disconnect() diff --git a/homeassistant/components/my_pv/strings.json b/homeassistant/components/my_pv/strings.json index 9e9ba5c1549b..5797e1a02bc3 100644 --- a/homeassistant/components/my_pv/strings.json +++ b/homeassistant/components/my_pv/strings.json @@ -7,7 +7,8 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unique_id_mismatch": "The device serial number does not match the original device." + "unique_id_mismatch": "The device serial number does not match the original device.", + "unsupported_device": "This my-PV device is not supported." }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", @@ -182,6 +183,9 @@ }, "unknown_error": { "message": "The device could not complete the requested action." + }, + "unsupported_device": { + "message": "This my-PV device is not supported." } } } diff --git a/tests/components/my_pv/test_config_flow.py b/tests/components/my_pv/test_config_flow.py index c5531c3474d0..873e43f2a8bb 100644 --- a/tests/components/my_pv/test_config_flow.py +++ b/tests/components/my_pv/test_config_flow.py @@ -3,7 +3,7 @@ from ipaddress import ip_address from unittest.mock import AsyncMock -from my_pv.exceptions import MyPVAuthenticationError +from my_pv.exceptions import MyPVAuthenticationError, MyPVDeviceNotSupportedError import pytest from homeassistant import config_entries @@ -90,6 +90,32 @@ async def test_step_user_already_configured( assert result["reason"] == "already_configured" +async def test_step_user_unsupported_device( + hass: HomeAssistant, + mock_my_pv_client: AsyncMock, +) -> None: + """Test if we get the local setup form with error if the device is not supported.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + assert not result["errors"] + + mock_my_pv_client.connect.side_effect = MyPVDeviceNotSupportedError + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "127.0.0.1", + }, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "unsupported_device" + + async def test_step_user_cannot_connect( hass: HomeAssistant, mock_my_pv_client: AsyncMock, @@ -299,6 +325,41 @@ async def test_step_discovery_already_configured( assert result["reason"] == "already_configured" +@pytest.mark.parametrize( + ("source", "data"), + [ + ( + config_entries.SOURCE_DHCP, + DHCP_DISCOVERY, + ), + ( + config_entries.SOURCE_ZEROCONF, + ZEROCONF_DISCOVERY, + ), + ], +) +@pytest.mark.usefixtures("mock_my_pv_client") +async def test_step_discovery_unsupported_device( + hass: HomeAssistant, + mock_my_pv_client: AsyncMock, + source: str, + data: BaseServiceInfo, +) -> None: + """Test discovery of an unsupported device.""" + mock_my_pv_client.connect.side_effect = MyPVDeviceNotSupportedError + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": source, + }, + data=data, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "unsupported_device" + + @pytest.mark.parametrize( ("source", "data"), [ diff --git a/tests/components/my_pv/test_init.py b/tests/components/my_pv/test_init.py index 0ef74efc0411..541abef9181c 100644 --- a/tests/components/my_pv/test_init.py +++ b/tests/components/my_pv/test_init.py @@ -2,7 +2,11 @@ from unittest.mock import AsyncMock -from my_pv.exceptions import MyPVAuthenticationError, MyPVConnectionError +from my_pv.exceptions import ( + MyPVAuthenticationError, + MyPVConnectionError, + MyPVDeviceNotSupportedError, +) import pytest from homeassistant.config_entries import ConfigEntryState @@ -41,6 +45,23 @@ async def test_setup_entry_cannot_connect( assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY +async def test_setup_entry_unsupported_device( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_my_pv_client: AsyncMock, +) -> None: + """Test setup of a config entry when device is not supported.""" + mock_config_entry.add_to_hass(hass) + + mock_my_pv_client.connect.side_effect = MyPVDeviceNotSupportedError() + + assert not await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR + assert mock_config_entry.error_reason_translation_key == "unsupported_device" + + async def test_setup_entry_auth_error( hass: HomeAssistant, mock_config_entry: MockConfigEntry,