From d71b1246cf084dfcbf47bd60e86478670d0cae91 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 10 Sep 2025 22:57:46 +0200 Subject: [PATCH] Add DHCP discovery to Aladdin Connect (#151532) --- .../components/aladdin_connect/manifest.json | 5 + .../components/aladdin_connect/strings.json | 3 + homeassistant/generated/dhcp.py | 4 + .../aladdin_connect/test_config_flow.py | 97 +++++++++++++++++++ 4 files changed, 109 insertions(+) diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index 67c755e29a8e..8165ebd4ac99 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -4,6 +4,11 @@ "codeowners": ["@swcloudgenie"], "config_flow": true, "dependencies": ["application_credentials"], + "dhcp": [ + { + "hostname": "gdocntl-*" + } + ], "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", "integration_type": "hub", "iot_class": "cloud_polling", diff --git a/homeassistant/components/aladdin_connect/strings.json b/homeassistant/components/aladdin_connect/strings.json index ca13d004b625..7d673efd3cb6 100644 --- a/homeassistant/components/aladdin_connect/strings.json +++ b/homeassistant/components/aladdin_connect/strings.json @@ -7,6 +7,9 @@ "reauth_confirm": { "title": "[%key:common::config_flow::title::reauth%]", "description": "Aladdin Connect needs to re-authenticate your account" + }, + "oauth_discovery": { + "description": "Home Assistant has found an Aladdin Connect device on your network. Press **Submit** to continue setting up Aladdin Connect." } }, "abort": { diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 3c1d929b1d85..ab95b106551b 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -26,6 +26,10 @@ DHCP: Final[list[dict[str, str | bool]]] = [ "domain": "airzone", "macaddress": "E84F25*", }, + { + "domain": "aladdin_connect", + "hostname": "gdocntl-*", + }, { "domain": "august", "hostname": "connect", diff --git a/tests/components/aladdin_connect/test_config_flow.py b/tests/components/aladdin_connect/test_config_flow.py index c0aafe933701..d69c588a6490 100644 --- a/tests/components/aladdin_connect/test_config_flow.py +++ b/tests/components/aladdin_connect/test_config_flow.py @@ -10,9 +10,11 @@ from homeassistant.components.aladdin_connect.const import ( OAUTH2_AUTHORIZE, OAUTH2_TOKEN, ) +from homeassistant.config_entries import SOURCE_DHCP from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers import config_entry_oauth2_flow +from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from .const import CLIENT_ID, USER_ID @@ -95,6 +97,79 @@ async def test_full_flow( assert result["result"].unique_id == USER_ID +@pytest.mark.usefixtures("current_request_with_host") +async def test_full_dhcp_flow( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + aioclient_mock: AiohttpClientMocker, + access_token, +) -> None: + """Check full flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=DhcpServiceInfo( + ip="192.168.0.123", macaddress="001122334455", hostname="gdocntl-334455" + ), + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "oauth_discovery" + assert not result["errors"] + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {}, + ) + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) + + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "refresh_token": "mock-refresh-token", + "access_token": access_token, + "type": "Bearer", + "expires_in": 60, + }, + ) + + with patch( + "homeassistant.components.aladdin_connect.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == "Aladdin Connect" + assert result["data"] == { + "auth_implementation": DOMAIN, + "token": { + "access_token": access_token, + "refresh_token": "mock-refresh-token", + "expires_in": 60, + "expires_at": result["data"]["token"]["expires_at"], + "type": "Bearer", + }, + } + assert result["result"].unique_id == USER_ID + + @pytest.mark.usefixtures("current_request_with_host") async def test_duplicate_entry( hass: HomeAssistant, @@ -146,6 +221,28 @@ async def test_duplicate_entry( assert result["reason"] == "already_configured" +@pytest.mark.usefixtures("current_request_with_host") +async def test_duplicate_dhcp_entry( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + aioclient_mock: AiohttpClientMocker, + access_token, + mock_config_entry: MockConfigEntry, +) -> None: + """Check full flow.""" + mock_config_entry.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DHCP}, + data=DhcpServiceInfo( + ip="192.168.0.123", macaddress="001122334455", hostname="gdocntl-334455" + ), + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + @pytest.mark.usefixtures("current_request_with_host") async def test_flow_reauth( hass: HomeAssistant,