Skip to content

Commit

Permalink
Extend dhcp discovery flow for ring integration (#126661)
Browse files Browse the repository at this point in the history
  • Loading branch information
sdb9696 authored Sep 27, 2024
1 parent e6af8f6 commit 33d0343
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 0 deletions.
23 changes: 23 additions & 0 deletions homeassistant/components/ring/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
from ring_doorbell import Auth, AuthenticationError, Requires2FAError
import voluptuous as vol

from homeassistant.components import dhcp
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.device_registry as dr

from . import get_auth_agent_id
from .const import CONF_2FA, DOMAIN
Expand All @@ -23,6 +25,8 @@
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})

UNKNOWN_RING_ACCOUNT = "unknown_ring_account"


async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, Any]:
"""Validate the user input allows us to connect."""
Expand Down Expand Up @@ -56,6 +60,25 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
user_pass: dict[str, Any] = {}
reauth_entry: ConfigEntry | None = None

async def async_step_dhcp(
self, discovery_info: dhcp.DhcpServiceInfo
) -> ConfigFlowResult:
"""Handle discovery via dhcp."""
# Ring has a single config entry per cloud username rather than per device
# so we check whether that device is already configured.
# If the device is not configured there's either no ring config entry
# yet or the device is registered to a different account
await self.async_set_unique_id(UNKNOWN_RING_ACCOUNT)
self._abort_if_unique_id_configured()
if self.hass.config_entries.async_has_entries(DOMAIN):
device_registry = dr.async_get(self.hass)
if device_registry.async_get_device(
identifiers={(DOMAIN, discovery_info.macaddress)}
):
return self.async_abort(reason="already_configured")

return await self.async_step_user()

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
Expand Down
16 changes: 16 additions & 0 deletions homeassistant/components/ring/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@
{
"hostname": "ring*",
"macaddress": "0CAE7D*"
},
{
"hostname": "ring*",
"macaddress": "2CAB33*"
},
{
"hostname": "ring*",
"macaddress": "94E36D*"
},
{
"hostname": "ring*",
"macaddress": "9C7613*"
},
{
"hostname": "ring*",
"macaddress": "341513*"
}
],
"documentation": "https://www.home-assistant.io/integrations/ring",
Expand Down
20 changes: 20 additions & 0 deletions homeassistant/generated/dhcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,26 @@
"hostname": "ring*",
"macaddress": "0CAE7D*",
},
{
"domain": "ring",
"hostname": "ring*",
"macaddress": "2CAB33*",
},
{
"domain": "ring",
"hostname": "ring*",
"macaddress": "94E36D*",
},
{
"domain": "ring",
"hostname": "ring*",
"macaddress": "9C7613*",
},
{
"domain": "ring",
"hostname": "ring*",
"macaddress": "341513*",
},
{
"domain": "roomba",
"hostname": "irobot-*",
Expand Down
56 changes: 56 additions & 0 deletions tests/components/ring/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
import ring_doorbell

from homeassistant import config_entries
from homeassistant.components import dhcp
from homeassistant.components.ring import DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import device_registry as dr

from tests.common import MockConfigEntry

Expand Down Expand Up @@ -242,3 +244,57 @@ async def test_account_configured(

assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "already_configured"


async def test_dhcp_discovery(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_ring_client: Mock,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test discovery by dhcp."""
mac_address = "1234567890abcd"
hostname = "Ring-90abcd"
ip_address = "127.0.0.1"
username = "[email protected]"

result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
ip=ip_address, macaddress=mac_address, hostname=hostname
),
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"username": username, "password": "test-password"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "[email protected]"
assert result["data"] == {
"username": username,
"token": {"access_token": "mock-token"},
}

config_entry = hass.config_entries.async_entry_for_domain_unique_id(
DOMAIN, username
)
assert config_entry

# Create a device entry under the config entry just created
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, mac_address)},
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
ip=ip_address, macaddress=mac_address, hostname=hostname
),
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"

0 comments on commit 33d0343

Please sign in to comment.