Set and check unique id of config in NUT (#141783)

* Set and check unique id in config

* Update homeassistant/components/nut/config_flow.py

Set unique ID and abort only if value is defined

Co-authored-by: J. Nick Koston <nick+github@koston.org>

* Add duplicate ID test case for multiple devices

* Add unique ID check to config flow step for UPS

* Update homeassistant/components/nut/__init__.py

Fix to only set config_entries unique ID if not None

Co-authored-by: J. Nick Koston <nick+github@koston.org>

* Remove duplicate config flow call

---------

Co-authored-by: J. Nick Koston <nick+github@koston.org>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
tdfountain 2025-03-30 14:41:56 -07:00 committed by GitHub
parent 3ab2cd3fb7
commit 1c16fb8e42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 114 additions and 3 deletions

View File

@ -121,6 +121,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: NutConfigEntry) -> bool:
if unique_id is None:
unique_id = entry.entry_id
elif entry.unique_id is None:
hass.config_entries.async_update_entry(entry, unique_id=unique_id)
if username is not None and password is not None:
# Dynamically add outlet integration commands
additional_integration_commands = set()

View File

@ -22,7 +22,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from . import PyNUTData
from . import PyNUTData, _unique_id_from_status
from .const import DEFAULT_HOST, DEFAULT_PORT, DOMAIN
_LOGGER = logging.getLogger(__name__)
@ -119,6 +119,11 @@ class NutConfigFlow(ConfigFlow, domain=DOMAIN):
if self._host_port_alias_already_configured(nut_config):
return self.async_abort(reason="already_configured")
if unique_id := _unique_id_from_status(info["available_resources"]):
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()
title = _format_host_port_alias(nut_config)
return self.async_create_entry(title=title, data=nut_config)
@ -141,8 +146,13 @@ class NutConfigFlow(ConfigFlow, domain=DOMAIN):
self.nut_config.update(user_input)
if self._host_port_alias_already_configured(nut_config):
return self.async_abort(reason="already_configured")
_, errors, placeholders = await self._async_validate_or_error(nut_config)
info, errors, placeholders = await self._async_validate_or_error(nut_config)
if not errors:
if unique_id := _unique_id_from_status(info["available_resources"]):
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()
title = _format_host_port_alias(nut_config)
return self.async_create_entry(title=title, data=nut_config)

View File

@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from .util import _get_mock_nutclient
from .util import _get_mock_nutclient, async_init_integration
from tests.common import MockConfigEntry
@ -524,6 +524,104 @@ async def test_abort_if_already_setup(hass: HomeAssistant) -> None:
assert result2["reason"] == "already_configured"
async def test_abort_duplicate_unique_ids(hass: HomeAssistant) -> None:
"""Test we abort if unique_id is already setup."""
list_vars = {
"device.mfr": "Some manufacturer",
"device.model": "Some model",
"device.serial": "0000-1",
}
await async_init_integration(
hass,
list_ups={"ups1": "UPS 1"},
list_vars=list_vars,
)
mock_pynut = _get_mock_nutclient(list_ups={"ups2": "UPS 2"}, list_vars=list_vars)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.nut.AIONUTClient",
return_value=mock_pynut,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: "1.1.1.1",
CONF_PORT: 2222,
},
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "already_configured"
async def test_abort_multiple_ups_duplicate_unique_ids(hass: HomeAssistant) -> None:
"""Test we abort on multiple devices if unique_id is already setup."""
list_vars = {
"device.mfr": "Some manufacturer",
"device.model": "Some model",
"device.serial": "0000-1",
}
mock_pynut = _get_mock_nutclient(
list_ups={"ups2": "UPS 2", "ups3": "UPS 3"}, list_vars=list_vars
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
with patch(
"homeassistant.components.nut.AIONUTClient",
return_value=mock_pynut,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_HOST: "1.1.1.1",
CONF_PORT: 2222,
},
)
await hass.async_block_till_done()
assert result2["step_id"] == "ups"
assert result2["type"] is FlowResultType.FORM
await async_init_integration(
hass,
list_ups={"ups1": "UPS 1"},
list_vars=list_vars,
)
with (
patch(
"homeassistant.components.nut.AIONUTClient",
return_value=mock_pynut,
),
patch(
"homeassistant.components.nut.async_setup_entry",
return_value=True,
),
):
result3 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_ALIAS: "ups2"},
)
await hass.async_block_till_done()
assert result3["type"] is FlowResultType.ABORT
assert result3["reason"] == "already_configured"
async def test_abort_if_already_setup_alias(hass: HomeAssistant) -> None:
"""Test we abort if component is already setup with same alias."""
config_entry = MockConfigEntry(