diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py index 0b2785f77bc..fbae0d5552a 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -279,8 +279,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: SamsungTVConfigEntry) - async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Migrate old entry.""" version = config_entry.version + minor_version = config_entry.minor_version - LOGGER.debug("Migrating from version %s", version) + LOGGER.debug("Migrating from version %s.%s", version, minor_version) # 1 -> 2: Unique ID format changed, so delete and re-import: if version == 1: @@ -293,6 +294,20 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> version = 2 hass.config_entries.async_update_entry(config_entry, version=2) - LOGGER.debug("Migration to version %s successful", version) + if version == 2: + if minor_version < 2: + # Cleanup invalid MAC addresses - see #103512 + dev_reg = dr.async_get(hass) + for device in dr.async_entries_for_config_entry( + dev_reg, config_entry.entry_id + ): + for connection in device.connections: + if connection == (dr.CONNECTION_NETWORK_MAC, "none"): + dev_reg.async_remove_device(device.id) + + minor_version = 2 + hass.config_entries.async_update_entry(config_entry, minor_version=2) + + LOGGER.debug("Migration to version %s.%s successful", version, minor_version) return True diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index 4845fb4fb74..e89c5e59b0e 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -101,6 +101,7 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a Samsung TV config flow.""" VERSION = 2 + MINOR_VERSION = 2 def __init__(self) -> None: """Initialize flow.""" diff --git a/tests/components/samsungtv/snapshots/test_init.ambr b/tests/components/samsungtv/snapshots/test_init.ambr index 1b8cf4c999d..42a3f4fb396 100644 --- a/tests/components/samsungtv/snapshots/test_init.ambr +++ b/tests/components/samsungtv/snapshots/test_init.ambr @@ -1,4 +1,80 @@ # serializer version: 1 +# name: test_cleanup_mac + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + 'aa:bb:cc:dd:ee:ff', + ), + tuple( + 'mac', + 'none', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'samsungtv', + 'any', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': None, + 'model': '82GXARRS', + 'name': 'fake', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_cleanup_mac.1 + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + 'aa:bb:cc:dd:ee:ff', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'samsungtv', + 'any', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': None, + 'model': '82GXARRS', + 'name': 'fake', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- # name: test_setup_updates_from_ssdp StateSnapshot({ 'attributes': ReadOnlyDict({ diff --git a/tests/components/samsungtv/test_diagnostics.py b/tests/components/samsungtv/test_diagnostics.py index fb280e26fda..7b20002ae5b 100644 --- a/tests/components/samsungtv/test_diagnostics.py +++ b/tests/components/samsungtv/test_diagnostics.py @@ -42,7 +42,7 @@ async def test_entry_diagnostics( "disabled_by": None, "domain": "samsungtv", "entry_id": "123456", - "minor_version": 1, + "minor_version": 2, "options": {}, "pref_disable_new_entities": False, "pref_disable_polling": False, @@ -79,7 +79,7 @@ async def test_entry_diagnostics_encrypted( "disabled_by": None, "domain": "samsungtv", "entry_id": "123456", - "minor_version": 1, + "minor_version": 2, "options": {}, "pref_disable_new_entities": False, "pref_disable_polling": False, @@ -115,7 +115,7 @@ async def test_entry_diagnostics_encrypte_offline( "disabled_by": None, "domain": "samsungtv", "entry_id": "123456", - "minor_version": 1, + "minor_version": 2, "options": {}, "pref_disable_new_entities": False, "pref_disable_polling": False, diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index 14c85b2c636..4efcf62c1dd 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -33,10 +33,11 @@ from homeassistant.const import ( SERVICE_VOLUME_UP, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er from . import setup_samsungtv_entry from .const import ( + MOCK_ENTRY_WS_WITH_MAC, MOCK_ENTRYDATA_ENCRYPTED_WS, MOCK_ENTRYDATA_WS, MOCK_SSDP_DATA_MAIN_TV_AGENT_ST, @@ -216,3 +217,50 @@ async def test_incorrectly_formatted_mac_fixed(hass: HomeAssistant) -> None: config_entries = hass.config_entries.async_entries(SAMSUNGTV_DOMAIN) assert len(config_entries) == 1 assert config_entries[0].data[CONF_MAC] == "aa:bb:aa:aa:aa:aa" + + +@pytest.mark.usefixtures("remotews", "rest_api") +async def test_cleanup_mac( + hass: HomeAssistant, device_registry: dr.DeviceRegistry, snapshot: SnapshotAssertion +) -> None: + """Test for `none` mac cleanup #103512.""" + entry = MockConfigEntry( + domain=SAMSUNGTV_DOMAIN, + data=MOCK_ENTRY_WS_WITH_MAC, + entry_id="123456", + unique_id="any", + version=2, + minor_version=1, + ) + entry.add_to_hass(hass) + + # Setup initial device registry, with incorrect MAC + device_registry.async_get_or_create( + config_entry_id="123456", + connections={ + (dr.CONNECTION_NETWORK_MAC, "none"), + (dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff"), + }, + identifiers={("samsungtv", "any")}, + model="82GXARRS", + name="fake", + ) + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) + assert device_entries == snapshot + assert device_entries[0].connections == { + (dr.CONNECTION_NETWORK_MAC, "none"), + (dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff"), + } + + # Run setup, and ensure the NONE mac is removed + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id) + assert device_entries == snapshot + assert device_entries[0].connections == { + (dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff") + } + + assert entry.version == 2 + assert entry.minor_version == 2