From 0599550e04d04f2d583c5ebe3e6cdc68b8b7d116 Mon Sep 17 00:00:00 2001 From: David Bishop Date: Tue, 24 Mar 2026 09:11:20 -0700 Subject: [PATCH] Add DHCP discovery support to Whisker integration (#165635) Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Joostlek --- .../components/litterrobot/manifest.json | 3 + .../components/litterrobot/quality_scale.yaml | 4 +- .../components/litterrobot/strings.json | 1 + homeassistant/generated/dhcp.py | 4 ++ .../litterrobot/test_config_flow.py | 67 +++++++++++++++++++ 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/litterrobot/manifest.json b/homeassistant/components/litterrobot/manifest.json index de14c1796d4..b19afbaccdb 100644 --- a/homeassistant/components/litterrobot/manifest.json +++ b/homeassistant/components/litterrobot/manifest.json @@ -6,6 +6,9 @@ "dhcp": [ { "hostname": "litter-robot4" + }, + { + "hostname": "whiskerrobots" } ], "documentation": "https://www.home-assistant.io/integrations/litterrobot", diff --git a/homeassistant/components/litterrobot/quality_scale.yaml b/homeassistant/components/litterrobot/quality_scale.yaml index 6500573dea7..1b6b99cd313 100644 --- a/homeassistant/components/litterrobot/quality_scale.yaml +++ b/homeassistant/components/litterrobot/quality_scale.yaml @@ -42,9 +42,7 @@ rules: discovery-update-info: status: done comment: The integration is cloud-based - discovery: - status: todo - comment: Need to validate discovery + discovery: done docs-data-update: done docs-examples: done docs-known-limitations: done diff --git a/homeassistant/components/litterrobot/strings.json b/homeassistant/components/litterrobot/strings.json index 8efa6476a85..cfa57d1adaf 100644 --- a/homeassistant/components/litterrobot/strings.json +++ b/homeassistant/components/litterrobot/strings.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]", "unique_id_mismatch": "The Whisker account does not match the previously configured account. Please re-authenticate using the same account, or remove this integration and set it up again if you want to use a different account." diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 37c6f63a657..1d2e1847c84 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -401,6 +401,10 @@ DHCP: Final[list[dict[str, str | bool]]] = [ "domain": "litterrobot", "hostname": "litter-robot4", }, + { + "domain": "litterrobot", + "hostname": "whiskerrobots", + }, { "domain": "lyric", "hostname": "lyric-*", diff --git a/tests/components/litterrobot/test_config_flow.py b/tests/components/litterrobot/test_config_flow.py index 7cb3bece35b..ef6c66a1d83 100644 --- a/tests/components/litterrobot/test_config_flow.py +++ b/tests/components/litterrobot/test_config_flow.py @@ -10,11 +10,23 @@ from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from .common import ACCOUNT_USER_ID, CONFIG, DOMAIN from tests.common import MockConfigEntry +DHCP_DISCOVERY_LR4 = DhcpServiceInfo( + ip="192.168.1.100", + macaddress="aabbccddeeff", + hostname="litter-robot4", +) +DHCP_DISCOVERY_LR5 = DhcpServiceInfo( + ip="192.168.1.101", + macaddress="aabbccddeef0", + hostname="whiskerrobots", +) + async def test_full_flow(hass: HomeAssistant, mock_account) -> None: """Test full flow.""" @@ -257,3 +269,58 @@ async def test_reconfigure(hass: HomeAssistant, mock_account: Account) -> None: assert entry.unique_id == ACCOUNT_USER_ID assert entry.data[CONF_PASSWORD] == new_password assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_dhcp_discovery_already_configured(hass: HomeAssistant) -> None: + """Test DHCP discovery aborts when already configured.""" + MockConfigEntry( + domain=DOMAIN, + data=CONFIG[DOMAIN], + unique_id=ACCOUNT_USER_ID, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=DHCP_DISCOVERY_LR4, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +async def test_dhcp_discovery_full_flow( + hass: HomeAssistant, mock_account: Account +) -> None: + """Test DHCP discovery through to successful entry creation.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=DHCP_DISCOVERY_LR4, + ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + with ( + patch( + "homeassistant.components.litterrobot.config_flow.Account.connect", + return_value=mock_account, + ), + patch( + "homeassistant.components.litterrobot.config_flow.Account.user_id", + new_callable=PropertyMock, + return_value=ACCOUNT_USER_ID, + ), + patch( + "homeassistant.components.litterrobot.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], CONFIG[DOMAIN] + ) + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == CONFIG[DOMAIN][CONF_USERNAME] + assert result["data"] == CONFIG[DOMAIN] + assert result["result"].unique_id == ACCOUNT_USER_ID + assert len(mock_setup_entry.mock_calls) == 1