diff --git a/homeassistant/components/lifx/__init__.py b/homeassistant/components/lifx/__init__.py index 41aa58fb962..76d4b7e36c5 100644 --- a/homeassistant/components/lifx/__init__.py +++ b/homeassistant/components/lifx/__init__.py @@ -30,7 +30,7 @@ from .coordinator import LIFXUpdateCoordinator from .discovery import async_discover_devices, async_trigger_discovery from .manager import LIFXManager from .migration import async_migrate_entities_devices, async_migrate_legacy_entries -from .util import async_entry_is_legacy, async_get_legacy_entry +from .util import async_entry_is_legacy, async_get_legacy_entry, formatted_serial CONF_SERVER = "server" CONF_BROADCAST = "broadcast" @@ -218,6 +218,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: connection.async_stop() raise + serial = formatted_serial(coordinator.serial_number) + if serial != entry.unique_id: + # If the serial number of the device does not match the unique_id + # of the config entry, it likely means the DHCP lease has expired + # and the device has been assigned a new IP address. We need to + # wait for the next discovery to find the device at its new address + # and update the config entry so we do not mix up devices. + raise ConfigEntryNotReady( + f"Unexpected device found at {host}; expected {entry.unique_id}, found {serial}" + ) domain_data[entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/tests/components/lifx/test_binary_sensor.py b/tests/components/lifx/test_binary_sensor.py index 4b583eed475..d71a7eeaf0b 100644 --- a/tests/components/lifx/test_binary_sensor.py +++ b/tests/components/lifx/test_binary_sensor.py @@ -21,7 +21,6 @@ from homeassistant.util import dt as dt_util from . import ( DEFAULT_ENTRY_TITLE, IP_ADDRESS, - MAC_ADDRESS, SERIAL, _mocked_clean_bulb, _patch_config_flow_try_connect, @@ -38,7 +37,7 @@ async def test_hev_cycle_state(hass: HomeAssistant) -> None: domain=lifx.DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_clean_bulb() diff --git a/tests/components/lifx/test_button.py b/tests/components/lifx/test_button.py index b166aa05d66..d527229fe78 100644 --- a/tests/components/lifx/test_button.py +++ b/tests/components/lifx/test_button.py @@ -14,7 +14,6 @@ from homeassistant.setup import async_setup_component from . import ( DEFAULT_ENTRY_TITLE, IP_ADDRESS, - MAC_ADDRESS, SERIAL, _mocked_bulb, _patch_config_flow_try_connect, @@ -38,7 +37,7 @@ async def test_button_restart(hass: HomeAssistant) -> None: domain=DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -70,7 +69,7 @@ async def test_button_identify(hass: HomeAssistant) -> None: domain=DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() diff --git a/tests/components/lifx/test_diagnostics.py b/tests/components/lifx/test_diagnostics.py index 581f0516184..a72695502a4 100644 --- a/tests/components/lifx/test_diagnostics.py +++ b/tests/components/lifx/test_diagnostics.py @@ -7,7 +7,7 @@ from homeassistant.setup import async_setup_component from . import ( DEFAULT_ENTRY_TITLE, IP_ADDRESS, - MAC_ADDRESS, + SERIAL, _mocked_bulb, _mocked_clean_bulb, _mocked_infrared_bulb, @@ -30,7 +30,7 @@ async def test_bulb_diagnostics( domain=lifx.DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -77,7 +77,7 @@ async def test_clean_bulb_diagnostics( domain=lifx.DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_clean_bulb() @@ -129,7 +129,7 @@ async def test_infrared_bulb_diagnostics( domain=lifx.DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() @@ -177,7 +177,7 @@ async def test_legacy_multizone_bulb_diagnostics( domain=lifx.DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_light_strip() @@ -288,7 +288,7 @@ async def test_multizone_bulb_diagnostics( domain=lifx.DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_light_strip() diff --git a/tests/components/lifx/test_init.py b/tests/components/lifx/test_init.py index 3c813840faf..3f16cc44f41 100644 --- a/tests/components/lifx/test_init.py +++ b/tests/components/lifx/test_init.py @@ -5,6 +5,8 @@ from datetime import timedelta import socket from unittest.mock import patch +import pytest + from homeassistant.components import lifx from homeassistant.components.lifx import DOMAIN, discovery from homeassistant.config_entries import ConfigEntryState @@ -149,3 +151,23 @@ async def test_dns_error_at_startup(hass: HomeAssistant) -> None: await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY + + +async def test_config_entry_wrong_serial( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test config entry enters setup retry when serial mismatches.""" + mismatched_serial = f"{SERIAL[:-1]}0" + already_migrated_config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=mismatched_serial + ) + already_migrated_config_entry.add_to_hass(hass) + with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device(): + await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) + await hass.async_block_till_done() + assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY + + assert ( + "Unexpected device found at 127.0.0.1; expected aa:bb:cc:dd:ee:c0, found aa:bb:cc:dd:ee:cc" + in caplog.text + ) diff --git a/tests/components/lifx/test_select.py b/tests/components/lifx/test_select.py index d190cbe6b10..aa705418d55 100644 --- a/tests/components/lifx/test_select.py +++ b/tests/components/lifx/test_select.py @@ -13,7 +13,6 @@ from homeassistant.util import dt as dt_util from . import ( DEFAULT_ENTRY_TITLE, IP_ADDRESS, - MAC_ADDRESS, SERIAL, MockLifxCommand, _mocked_infrared_bulb, @@ -32,7 +31,7 @@ async def test_theme_select(hass: HomeAssistant) -> None: domain=DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_light_strip() @@ -70,7 +69,7 @@ async def test_infrared_brightness(hass: HomeAssistant) -> None: domain=DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() @@ -100,7 +99,7 @@ async def test_set_infrared_brightness_25_percent(hass: HomeAssistant) -> None: domain=DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() @@ -139,7 +138,7 @@ async def test_set_infrared_brightness_50_percent(hass: HomeAssistant) -> None: domain=DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() @@ -178,7 +177,7 @@ async def test_set_infrared_brightness_100_percent(hass: HomeAssistant) -> None: domain=DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() @@ -217,7 +216,7 @@ async def test_disable_infrared(hass: HomeAssistant) -> None: domain=DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() @@ -256,7 +255,7 @@ async def test_invalid_infrared_brightness(hass: HomeAssistant) -> None: domain=DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() diff --git a/tests/components/lifx/test_sensor.py b/tests/components/lifx/test_sensor.py index a36e151849b..5fe69c8dabc 100644 --- a/tests/components/lifx/test_sensor.py +++ b/tests/components/lifx/test_sensor.py @@ -20,7 +20,7 @@ from homeassistant.util import dt as dt_util from . import ( DEFAULT_ENTRY_TITLE, IP_ADDRESS, - MAC_ADDRESS, + SERIAL, _mocked_bulb, _mocked_bulb_old_firmware, _patch_config_flow_try_connect, @@ -38,7 +38,7 @@ async def test_rssi_sensor(hass: HomeAssistant) -> None: domain=lifx.DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -89,7 +89,7 @@ async def test_rssi_sensor_old_firmware(hass: HomeAssistant) -> None: domain=lifx.DOMAIN, title=DEFAULT_ENTRY_TITLE, data={CONF_HOST: IP_ADDRESS}, - unique_id=MAC_ADDRESS, + unique_id=SERIAL, ) config_entry.add_to_hass(hass) bulb = _mocked_bulb_old_firmware()