Fix tplink not updating IP from DHCP discovery and discovering twice (#110557)

We only called format_mac on the mac address if we connected
to the device during entry creation. Since the format of the
mac address from DHCP discovery did not match the format saved
in the unique id, the IP would not get updated and a second
discovery would appear

Thankfully the creation path does format the mac so we did not
create any entries with an inconsistantly formatted unique id

fixes #110460
This commit is contained in:
J. Nick Koston 2024-02-14 16:03:30 -06:00 committed by GitHub
parent c045e23000
commit cd1c633ff9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 56 additions and 7 deletions

View File

@ -61,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
"""Handle discovery via dhcp.""" """Handle discovery via dhcp."""
return await self._async_handle_discovery( return await self._async_handle_discovery(
discovery_info.ip, discovery_info.macaddress discovery_info.ip, dr.format_mac(discovery_info.macaddress)
) )
async def async_step_integration_discovery( async def async_step_integration_discovery(

View File

@ -36,6 +36,7 @@ IP_ADDRESS2 = "127.0.0.2"
ALIAS = "My Bulb" ALIAS = "My Bulb"
MODEL = "HS100" MODEL = "HS100"
MAC_ADDRESS = "aa:bb:cc:dd:ee:ff" MAC_ADDRESS = "aa:bb:cc:dd:ee:ff"
DHCP_FORMATTED_MAC_ADDRESS = MAC_ADDRESS.replace(":", "")
MAC_ADDRESS2 = "11:22:33:44:55:66" MAC_ADDRESS2 = "11:22:33:44:55:66"
DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}" DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}"
CREDENTIALS_HASH_LEGACY = "" CREDENTIALS_HASH_LEGACY = ""

View File

@ -33,6 +33,7 @@ from . import (
DEFAULT_ENTRY_TITLE, DEFAULT_ENTRY_TITLE,
DEVICE_CONFIG_DICT_AUTH, DEVICE_CONFIG_DICT_AUTH,
DEVICE_CONFIG_DICT_LEGACY, DEVICE_CONFIG_DICT_LEGACY,
DHCP_FORMATTED_MAC_ADDRESS,
IP_ADDRESS, IP_ADDRESS,
MAC_ADDRESS, MAC_ADDRESS,
MAC_ADDRESS2, MAC_ADDRESS2,
@ -144,6 +145,7 @@ async def test_discovery_auth(
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == DEFAULT_ENTRY_TITLE assert result2["title"] == DEFAULT_ENTRY_TITLE
assert result2["data"] == CREATE_ENTRY_DATA_AUTH assert result2["data"] == CREATE_ENTRY_DATA_AUTH
assert result2["context"]["unique_id"] == MAC_ADDRESS
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -206,6 +208,7 @@ async def test_discovery_auth_errors(
) )
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["data"] == CREATE_ENTRY_DATA_AUTH assert result3["data"] == CREATE_ENTRY_DATA_AUTH
assert result3["context"]["unique_id"] == MAC_ADDRESS
async def test_discovery_new_credentials( async def test_discovery_new_credentials(
@ -254,6 +257,7 @@ async def test_discovery_new_credentials(
) )
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["data"] == CREATE_ENTRY_DATA_AUTH assert result3["data"] == CREATE_ENTRY_DATA_AUTH
assert result3["context"]["unique_id"] == MAC_ADDRESS
async def test_discovery_new_credentials_invalid( async def test_discovery_new_credentials_invalid(
@ -309,6 +313,7 @@ async def test_discovery_new_credentials_invalid(
) )
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["data"] == CREATE_ENTRY_DATA_AUTH assert result3["data"] == CREATE_ENTRY_DATA_AUTH
assert result3["context"]["unique_id"] == MAC_ADDRESS
async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> None: async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> None:
@ -365,6 +370,7 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["title"] == DEFAULT_ENTRY_TITLE assert result3["title"] == DEFAULT_ENTRY_TITLE
assert result3["data"] == CREATE_ENTRY_DATA_LEGACY assert result3["data"] == CREATE_ENTRY_DATA_LEGACY
assert result3["context"]["unique_id"] == MAC_ADDRESS
await hass.async_block_till_done() await hass.async_block_till_done()
mock_setup_entry.assert_called_once() mock_setup_entry.assert_called_once()
@ -432,6 +438,7 @@ async def test_manual(hass: HomeAssistant) -> None:
assert result4["type"] is FlowResultType.CREATE_ENTRY assert result4["type"] is FlowResultType.CREATE_ENTRY
assert result4["title"] == DEFAULT_ENTRY_TITLE assert result4["title"] == DEFAULT_ENTRY_TITLE
assert result4["data"] == CREATE_ENTRY_DATA_LEGACY assert result4["data"] == CREATE_ENTRY_DATA_LEGACY
assert result4["context"]["unique_id"] == MAC_ADDRESS
# Duplicate # Duplicate
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -470,6 +477,7 @@ async def test_manual_no_capabilities(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == CREATE_ENTRY_DATA_LEGACY assert result["data"] == CREATE_ENTRY_DATA_LEGACY
assert result["context"]["unique_id"] == MAC_ADDRESS
async def test_manual_auth( async def test_manual_auth(
@ -510,6 +518,7 @@ async def test_manual_auth(
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["type"] is FlowResultType.CREATE_ENTRY
assert result3["title"] == DEFAULT_ENTRY_TITLE assert result3["title"] == DEFAULT_ENTRY_TITLE
assert result3["data"] == CREATE_ENTRY_DATA_AUTH assert result3["data"] == CREATE_ENTRY_DATA_AUTH
assert result3["context"]["unique_id"] == MAC_ADDRESS
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -572,6 +581,7 @@ async def test_manual_auth_errors(
) )
assert result4["type"] is FlowResultType.CREATE_ENTRY assert result4["type"] is FlowResultType.CREATE_ENTRY
assert result4["data"] == CREATE_ENTRY_DATA_AUTH assert result4["data"] == CREATE_ENTRY_DATA_AUTH
assert result4["context"]["unique_id"] == MAC_ADDRESS
await hass.async_block_till_done() await hass.async_block_till_done()
@ -599,7 +609,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_DHCP}, context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo( data=dhcp.DhcpServiceInfo(
ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
), ),
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -611,7 +621,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_DHCP}, context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo( data=dhcp.DhcpServiceInfo(
ip=IP_ADDRESS, macaddress="00:00:00:00:00:00", hostname="mock_hostname" ip=IP_ADDRESS, macaddress="000000000000", hostname="mock_hostname"
), ),
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -625,7 +635,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_DHCP}, context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo( data=dhcp.DhcpServiceInfo(
ip="1.2.3.5", macaddress="00:00:00:00:00:01", hostname="mock_hostname" ip="1.2.3.5", macaddress="000000000001", hostname="mock_hostname"
), ),
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -638,7 +648,9 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
[ [
( (
config_entries.SOURCE_DHCP, config_entries.SOURCE_DHCP,
dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS), dhcp.DhcpServiceInfo(
ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
),
), ),
( (
config_entries.SOURCE_INTEGRATION_DISCOVERY, config_entries.SOURCE_INTEGRATION_DISCOVERY,
@ -675,6 +687,8 @@ async def test_discovered_by_dhcp_or_discovery(
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["data"] == CREATE_ENTRY_DATA_LEGACY assert result2["data"] == CREATE_ENTRY_DATA_LEGACY
assert result2["context"]["unique_id"] == MAC_ADDRESS
assert mock_async_setup.called assert mock_async_setup.called
assert mock_async_setup_entry.called assert mock_async_setup_entry.called
@ -684,7 +698,9 @@ async def test_discovered_by_dhcp_or_discovery(
[ [
( (
config_entries.SOURCE_DHCP, config_entries.SOURCE_DHCP,
dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS), dhcp.DhcpServiceInfo(
ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
),
), ),
( (
config_entries.SOURCE_INTEGRATION_DISCOVERY, config_entries.SOURCE_INTEGRATION_DISCOVERY,
@ -713,7 +729,7 @@ async def test_discovered_by_dhcp_or_discovery_failed_to_get_device(
assert result["reason"] == "cannot_connect" assert result["reason"] == "cannot_connect"
async def test_discovery_with_ip_change( async def test_integration_discovery_with_ip_change(
hass: HomeAssistant, hass: HomeAssistant,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
mock_discovery: AsyncMock, mock_discovery: AsyncMock,
@ -764,6 +780,36 @@ async def test_discovery_with_ip_change(
mock_connect["connect"].assert_awaited_once_with(config=config) mock_connect["connect"].assert_awaited_once_with(config=config)
async def test_dhcp_discovery_with_ip_change(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_discovery: AsyncMock,
mock_connect: AsyncMock,
) -> None:
"""Test dhcp discovery with an IP change."""
mock_connect["connect"].side_effect = SmartDeviceException()
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state == config_entries.ConfigEntryState.SETUP_RETRY
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 0
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY
assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.1"
discovery_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
ip="127.0.0.2", macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS
),
)
assert discovery_result["type"] is FlowResultType.ABORT
assert discovery_result["reason"] == "already_configured"
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
async def test_reauth( async def test_reauth(
hass: HomeAssistant, hass: HomeAssistant,
mock_added_config_entry: MockConfigEntry, mock_added_config_entry: MockConfigEntry,
@ -1025,6 +1071,7 @@ async def test_pick_device_errors(
}, },
) )
assert result4["type"] == FlowResultType.CREATE_ENTRY assert result4["type"] == FlowResultType.CREATE_ENTRY
assert result4["context"]["unique_id"] == MAC_ADDRESS
async def test_discovery_timeout_connect( async def test_discovery_timeout_connect(
@ -1049,6 +1096,7 @@ async def test_discovery_timeout_connect(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["context"]["unique_id"] == MAC_ADDRESS
assert mock_connect["connect"].call_count == 1 assert mock_connect["connect"].call_count == 1