From 2e0038b9813ef71728502865c2c29ee233db6a9b Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Tue, 22 Aug 2023 02:22:46 -0500 Subject: [PATCH] Require device id for Roku entities (#98734) --- homeassistant/components/roku/__init__.py | 7 ++- .../components/roku/binary_sensor.py | 3 +- homeassistant/components/roku/coordinator.py | 2 + homeassistant/components/roku/entity.py | 52 ++++++++----------- homeassistant/components/roku/media_player.py | 3 +- homeassistant/components/roku/remote.py | 3 +- homeassistant/components/roku/select.py | 3 -- homeassistant/components/roku/sensor.py | 3 +- tests/components/roku/conftest.py | 6 ++- tests/components/roku/test_init.py | 19 +++++++ 10 files changed, 58 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index 583d26a4a5b..f31a07feb29 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -22,7 +22,12 @@ PLATFORMS = [ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Roku from a config entry.""" - coordinator = RokuDataUpdateCoordinator(hass, host=entry.data[CONF_HOST]) + if (device_id := entry.unique_id) is None: + device_id = entry.entry_id + + coordinator = RokuDataUpdateCoordinator( + hass, host=entry.data[CONF_HOST], device_id=device_id + ) await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator diff --git a/homeassistant/components/roku/binary_sensor.py b/homeassistant/components/roku/binary_sensor.py index 4bc36d0f7e5..1ac5f38b2c5 100644 --- a/homeassistant/components/roku/binary_sensor.py +++ b/homeassistant/components/roku/binary_sensor.py @@ -71,10 +71,9 @@ async def async_setup_entry( ) -> None: """Set up a Roku binary sensors based on a config entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] - unique_id = coordinator.data.info.serial_number + async_add_entities( RokuBinarySensorEntity( - device_id=unique_id, coordinator=coordinator, description=description, ) diff --git a/homeassistant/components/roku/coordinator.py b/homeassistant/components/roku/coordinator.py index f084302841e..a0bd9df238c 100644 --- a/homeassistant/components/roku/coordinator.py +++ b/homeassistant/components/roku/coordinator.py @@ -32,8 +32,10 @@ class RokuDataUpdateCoordinator(DataUpdateCoordinator[Device]): hass: HomeAssistant, *, host: str, + device_id: str, ) -> None: """Initialize global Roku data updater.""" + self.device_id = device_id self.roku = Roku(host=host, session=async_get_clientsession(hass)) self.full_update_interval = timedelta(minutes=15) diff --git a/homeassistant/components/roku/entity.py b/homeassistant/components/roku/entity.py index b6343d0dae1..b783831d4ec 100644 --- a/homeassistant/components/roku/entity.py +++ b/homeassistant/components/roku/entity.py @@ -12,45 +12,37 @@ from .const import DOMAIN class RokuEntity(CoordinatorEntity[RokuDataUpdateCoordinator]): """Defines a base Roku entity.""" + _attr_has_entity_name = True + def __init__( self, *, - device_id: str | None, coordinator: RokuDataUpdateCoordinator, description: EntityDescription | None = None, ) -> None: """Initialize the Roku entity.""" super().__init__(coordinator) - self._device_id = device_id if description is not None: self.entity_description = description + self._attr_unique_id = f"{coordinator.device_id}_{description.key}" + else: + self._attr_unique_id = coordinator.device_id - if device_id is None: - self._attr_name = f"{coordinator.data.info.name} {description.name}" - - if device_id is not None: - self._attr_has_entity_name = True - - if description is not None: - self._attr_unique_id = f"{device_id}_{description.key}" - else: - self._attr_unique_id = device_id - - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, device_id)}, - connections={ - (CONNECTION_NETWORK_MAC, mac_address) - for mac_address in ( - self.coordinator.data.info.wifi_mac, - self.coordinator.data.info.ethernet_mac, - ) - if mac_address is not None - }, - name=self.coordinator.data.info.name, - manufacturer=self.coordinator.data.info.brand, - model=self.coordinator.data.info.model_name, - hw_version=self.coordinator.data.info.model_number, - sw_version=self.coordinator.data.info.version, - suggested_area=self.coordinator.data.info.device_location, - ) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, coordinator.device_id)}, + connections={ + (CONNECTION_NETWORK_MAC, mac_address) + for mac_address in ( + self.coordinator.data.info.wifi_mac, + self.coordinator.data.info.ethernet_mac, + ) + if mac_address is not None + }, + name=self.coordinator.data.info.name, + manufacturer=self.coordinator.data.info.brand, + model=self.coordinator.data.info.model_name, + hw_version=self.coordinator.data.info.model_number, + sw_version=self.coordinator.data.info.version, + suggested_area=self.coordinator.data.info.device_location, + ) diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index a8c1cf4698c..05f782b37c4 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -85,11 +85,10 @@ async def async_setup_entry( ) -> None: """Set up the Roku config entry.""" coordinator: RokuDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - unique_id = coordinator.data.info.serial_number + async_add_entities( [ RokuMediaPlayer( - device_id=unique_id, coordinator=coordinator, ) ], diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 0271e4a0f73..ef5350eb741 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -22,11 +22,10 @@ async def async_setup_entry( ) -> None: """Load Roku remote based on a config entry.""" coordinator: RokuDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - unique_id = coordinator.data.info.serial_number + async_add_entities( [ RokuRemote( - device_id=unique_id, coordinator=coordinator, ) ], diff --git a/homeassistant/components/roku/select.py b/homeassistant/components/roku/select.py index e11748114d1..f915fdef9b0 100644 --- a/homeassistant/components/roku/select.py +++ b/homeassistant/components/roku/select.py @@ -122,14 +122,12 @@ async def async_setup_entry( """Set up Roku select based on a config entry.""" coordinator: RokuDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] device: RokuDevice = coordinator.data - unique_id = device.info.serial_number entities: list[RokuSelectEntity] = [] for description in ENTITIES: entities.append( RokuSelectEntity( - device_id=unique_id, coordinator=coordinator, description=description, ) @@ -138,7 +136,6 @@ async def async_setup_entry( if len(device.channels) > 0: entities.append( RokuSelectEntity( - device_id=unique_id, coordinator=coordinator, description=CHANNEL_ENTITY, ) diff --git a/homeassistant/components/roku/sensor.py b/homeassistant/components/roku/sensor.py index 0f0e87205b9..562501b4013 100644 --- a/homeassistant/components/roku/sensor.py +++ b/homeassistant/components/roku/sensor.py @@ -56,10 +56,9 @@ async def async_setup_entry( ) -> None: """Set up Roku sensor based on a config entry.""" coordinator: RokuDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - unique_id = coordinator.data.info.serial_number + async_add_entities( RokuSensorEntity( - device_id=unique_id, coordinator=coordinator, description=description, ) diff --git a/tests/components/roku/conftest.py b/tests/components/roku/conftest.py index 677a10c697c..c1ceb23934e 100644 --- a/tests/components/roku/conftest.py +++ b/tests/components/roku/conftest.py @@ -81,9 +81,13 @@ def mock_roku( @pytest.fixture async def init_integration( - hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_roku: MagicMock + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_device: RokuDevice, + mock_roku: MagicMock, ) -> MockConfigEntry: """Set up the Roku integration for testing.""" + mock_config_entry.unique_id = mock_device.info.serial_number mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) diff --git a/tests/components/roku/test_init.py b/tests/components/roku/test_init.py index f8820e711a2..2ebad1e0b2b 100644 --- a/tests/components/roku/test_init.py +++ b/tests/components/roku/test_init.py @@ -26,6 +26,25 @@ async def test_config_entry_not_ready( assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY +async def test_config_entry_no_unique_id( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_roku: AsyncMock, +) -> None: + """Test the Roku configuration entry with missing unique id.""" + mock_config_entry.unique_id = None + 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.entry_id in hass.data[DOMAIN] + assert mock_config_entry.state is ConfigEntryState.LOADED + assert ( + hass.data[DOMAIN][mock_config_entry.entry_id].device_id + == mock_config_entry.entry_id + ) + + async def test_load_unload_config_entry( hass: HomeAssistant, mock_config_entry: MockConfigEntry,