"""The tests for the Update component.""" from collections.abc import Generator from unittest.mock import MagicMock, patch import pytest from homeassistant.components.update import ( ATTR_BACKUP, ATTR_VERSION, DOMAIN, SERVICE_INSTALL, SERVICE_SKIP, UpdateDeviceClass, UpdateEntity, UpdateEntityDescription, ) from homeassistant.components.update.const import ( ATTR_AUTO_UPDATE, ATTR_IN_PROGRESS, ATTR_INSTALLED_VERSION, ATTR_LATEST_VERSION, ATTR_RELEASE_SUMMARY, ATTR_RELEASE_URL, ATTR_SKIPPED_VERSION, ATTR_TITLE, UpdateEntityFeature, ) from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import ( ATTR_ENTITY_ID, CONF_PLATFORM, STATE_OFF, STATE_ON, STATE_UNKNOWN, EntityCategory, ) from homeassistant.core import HomeAssistant, State, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_state_change_event from homeassistant.setup import async_setup_component from tests.common import ( MockConfigEntry, MockEntityPlatform, MockModule, MockPlatform, mock_config_flow, mock_integration, mock_platform, mock_restore_cache, setup_test_component_platform, ) from tests.typing import WebSocketGenerator TEST_DOMAIN = "test" class MockUpdateEntity(UpdateEntity): """Mock UpdateEntity to use in tests.""" async def test_update(hass: HomeAssistant) -> None: """Test getting data from the mocked update entity.""" update = MockUpdateEntity() update.hass = hass update.platform = MockEntityPlatform(hass) update._attr_installed_version = "1.0.0" update._attr_latest_version = "1.0.1" update._attr_release_summary = "Summary" update._attr_release_url = "https://example.com" update._attr_title = "Title" assert update.entity_category is EntityCategory.DIAGNOSTIC assert ( update.entity_picture == "https://brands.home-assistant.io/_/test_platform/icon.png" ) assert update.installed_version == "1.0.0" assert update.latest_version == "1.0.1" assert update.release_summary == "Summary" assert update.release_url == "https://example.com" assert update.title == "Title" assert update.in_progress is False assert update.state == STATE_ON assert update.state_attributes == { ATTR_AUTO_UPDATE: False, ATTR_INSTALLED_VERSION: "1.0.0", ATTR_IN_PROGRESS: False, ATTR_LATEST_VERSION: "1.0.1", ATTR_RELEASE_SUMMARY: "Summary", ATTR_RELEASE_URL: "https://example.com", ATTR_SKIPPED_VERSION: None, ATTR_TITLE: "Title", } # Test no update available update._attr_installed_version = "1.0.0" update._attr_latest_version = "1.0.0" assert update.state is STATE_OFF # Test state becomes unknown if installed version is unknown update._attr_installed_version = None update._attr_latest_version = "1.0.0" assert update.state is None # Test state becomes unknown if latest version is unknown update._attr_installed_version = "1.0.0" update._attr_latest_version = None assert update.state is None # Test no update if new version is not an update update._attr_installed_version = "1.0.0" update._attr_latest_version = "0.9.0" assert update.state is STATE_OFF # Test update if new version is not considered a valid version update._attr_installed_version = "1.0.0" update._attr_latest_version = "awesome_update" assert update.state is STATE_ON # Test entity category becomes config when its possible to install update._attr_supported_features = UpdateEntityFeature.INSTALL assert update.entity_category is EntityCategory.CONFIG # UpdateEntityDescription was set update._attr_supported_features = 0 update.entity_description = UpdateEntityDescription(key="F5 - Its very refreshing") assert update.device_class is None assert update.entity_category is EntityCategory.CONFIG del update.device_class update.entity_description = UpdateEntityDescription( key="F5 - Its very refreshing", device_class=UpdateDeviceClass.FIRMWARE, entity_category=None, ) assert update.device_class is UpdateDeviceClass.FIRMWARE assert update.entity_category is None # Device class via attribute (override entity description) update._attr_device_class = None assert update.device_class is None update._attr_device_class = UpdateDeviceClass.FIRMWARE assert update.device_class is UpdateDeviceClass.FIRMWARE # Entity Attribute via attribute (override entity description) update._attr_entity_category = None assert update.entity_category is None update._attr_entity_category = EntityCategory.DIAGNOSTIC assert update.entity_category is EntityCategory.DIAGNOSTIC with pytest.raises(NotImplementedError): await update.async_install(version=None, backup=True) with pytest.raises(NotImplementedError): update.install(version=None, backup=False) update.install = MagicMock() await update.async_install(version="1.0.1", backup=True) assert update.install.called assert update.install.call_args[0][0] == "1.0.1" assert update.install.call_args[0][1] is True async def test_entity_with_no_install( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], ) -> None: """Test entity with no updates.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() # Update is available state = hass.states.get("update.update_no_install") assert state assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" # Should not be able to install as the entity doesn't support that with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_ENTITY_ID: "update.update_no_install"}, blocking=True, ) # Nothing changed state = hass.states.get("update.update_no_install") assert state assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert state.attributes[ATTR_SKIPPED_VERSION] is None # We can mark the update as skipped await hass.services.async_call( DOMAIN, SERVICE_SKIP, {ATTR_ENTITY_ID: "update.update_no_install"}, blocking=True, ) state = hass.states.get("update.update_no_install") assert state assert state.state == STATE_OFF assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert state.attributes[ATTR_SKIPPED_VERSION] == "1.0.1" # We can clear the skipped marker again await hass.services.async_call( DOMAIN, "clear_skipped", {ATTR_ENTITY_ID: "update.update_no_install"}, blocking=True, ) state = hass.states.get("update.update_no_install") assert state assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert state.attributes[ATTR_SKIPPED_VERSION] is None async def test_entity_with_no_updates( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], ) -> None: """Test entity with no updates.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() # No update available state = hass.states.get("update.no_update") assert state assert state.state == STATE_OFF assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.0" # Should not be able to skip when there is no update available with pytest.raises(HomeAssistantError, match="No update available to skip for"): await hass.services.async_call( DOMAIN, SERVICE_SKIP, {ATTR_ENTITY_ID: "update.no_update"}, blocking=True, ) # Should not be able to install an update when there is no update available with pytest.raises(HomeAssistantError, match="No update available for"): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_ENTITY_ID: "update.no_update"}, blocking=True, ) # Updating to a specific version is not supported by this entity with pytest.raises( HomeAssistantError, match="Installing a specific version is not supported for", ): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_VERSION: "0.9.0", ATTR_ENTITY_ID: "update.no_update"}, blocking=True, ) async def test_entity_with_auto_update( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity that has auto update feature.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() state = hass.states.get("update.update_with_auto_update") assert state assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert state.attributes[ATTR_SKIPPED_VERSION] is None # Should be able to manually install an update even if it can auto update await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_ENTITY_ID: "update.update_with_auto_update"}, blocking=True, ) # Should not be able to skip the update with pytest.raises( HomeAssistantError, match="Skipping update is not supported for update.update_with_auto_update", ): await hass.services.async_call( DOMAIN, SERVICE_SKIP, {ATTR_ENTITY_ID: "update.update_with_auto_update"}, blocking=True, ) # Should not be able to clear a skipped the update with pytest.raises( HomeAssistantError, match="Clearing skipped update is not supported for update.update_with_auto_update", ): await hass.services.async_call( DOMAIN, "clear_skipped", {ATTR_ENTITY_ID: "update.update_with_auto_update"}, blocking=True, ) async def test_entity_with_updates_available( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test basic update entity with updates available.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() # Entity has an update available state = hass.states.get("update.update_available") assert state assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert state.attributes[ATTR_SKIPPED_VERSION] is None # Skip skip the update await hass.services.async_call( DOMAIN, SERVICE_SKIP, {ATTR_ENTITY_ID: "update.update_available"}, blocking=True, ) # The state should have changed to off, skipped version should be set state = hass.states.get("update.update_available") assert state assert state.state == STATE_OFF assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert state.attributes[ATTR_SKIPPED_VERSION] == "1.0.1" # Even though skipped, we can still update if we want to await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_ENTITY_ID: "update.update_available"}, blocking=True, ) # The state should have changed to off, skipped version should be set state = hass.states.get("update.update_available") assert state assert state.state == STATE_OFF assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.1" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert state.attributes[ATTR_SKIPPED_VERSION] is None assert "Installed latest update" in caplog.text async def test_entity_with_unknown_version( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity that has an unknown version.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() state = hass.states.get("update.update_unknown") assert state assert state.state == STATE_UNKNOWN assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] is None assert state.attributes[ATTR_SKIPPED_VERSION] is None # Should not be able to install an update when there is no update available with pytest.raises(HomeAssistantError, match="No update available for"): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_ENTITY_ID: "update.update_unknown"}, blocking=True, ) # Should not be to skip the update with pytest.raises(HomeAssistantError, match="Cannot skip an unknown version for"): await hass.services.async_call( DOMAIN, SERVICE_SKIP, {ATTR_ENTITY_ID: "update.update_unknown"}, blocking=True, ) async def test_entity_with_specific_version( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity that support specific version.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() state = hass.states.get("update.update_specific_version") assert state assert state.state == STATE_OFF assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.0" # Update to a specific version await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_VERSION: "0.9.9", ATTR_ENTITY_ID: "update.update_specific_version"}, blocking=True, ) # Version has changed, state should be on as there is an update available state = hass.states.get("update.update_specific_version") assert state assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "0.9.9" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.0" assert "Installed update with version: 0.9.9" in caplog.text # Update back to the latest version await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_ENTITY_ID: "update.update_specific_version"}, blocking=True, ) state = hass.states.get("update.update_specific_version") assert state assert state.state == STATE_OFF assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.0" assert "Installed latest update" in caplog.text # This entity does not support doing a backup before upgrade with pytest.raises(HomeAssistantError, match="Backup is not supported for"): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, { ATTR_VERSION: "0.9.9", ATTR_BACKUP: True, ATTR_ENTITY_ID: "update.update_specific_version", }, blocking=True, ) async def test_entity_with_backup_support( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity with backup support.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() # This entity support backing up before install the update state = hass.states.get("update.update_backup") assert state assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" # Without a backup await hass.services.async_call( DOMAIN, SERVICE_INSTALL, { ATTR_BACKUP: False, ATTR_ENTITY_ID: "update.update_backup", }, blocking=True, ) state = hass.states.get("update.update_backup") assert state assert state.state == STATE_OFF assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.1" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert "Creating backup before installing update" not in caplog.text assert "Installed latest update" in caplog.text # Specific version, do create a backup this time await hass.services.async_call( DOMAIN, SERVICE_INSTALL, { ATTR_BACKUP: True, ATTR_VERSION: "0.9.8", ATTR_ENTITY_ID: "update.update_backup", }, blocking=True, ) # This entity support backing up before install the update state = hass.states.get("update.update_backup") assert state assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "0.9.8" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert "Creating backup before installing update" in caplog.text assert "Installed update with version: 0.9.8" in caplog.text async def test_entity_already_in_progress( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update install already in progress.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() state = hass.states.get("update.update_already_in_progress") assert state assert state.state == STATE_ON assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert state.attributes[ATTR_IN_PROGRESS] == 50 with pytest.raises( HomeAssistantError, match="Update installation already in progress for", ): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_ENTITY_ID: "update.update_already_in_progress"}, blocking=True, ) async def test_entity_without_progress_support( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity without progress support. In that case, progress is still handled by Home Assistant. """ setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() events = [] async_track_state_change_event( hass, "update.update_available", callback(lambda event: events.append(event)) ) await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_ENTITY_ID: "update.update_available"}, blocking=True, ) assert len(events) == 2 assert events[0].data.get("old_state").attributes[ATTR_IN_PROGRESS] is False assert events[0].data.get("old_state").attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert events[0].data.get("new_state").attributes[ATTR_IN_PROGRESS] is True assert events[0].data.get("new_state").attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert events[1].data.get("old_state").attributes[ATTR_IN_PROGRESS] is True assert events[1].data.get("old_state").attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert events[1].data.get("new_state").attributes[ATTR_IN_PROGRESS] is False assert events[1].data.get("new_state").attributes[ATTR_INSTALLED_VERSION] == "1.0.1" async def test_entity_without_progress_support_raising( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity without progress support that raises during install. In that case, progress is still handled by Home Assistant. """ setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() events = [] async_track_state_change_event( hass, "update.update_available", callback(lambda event: events.append(event)) ) with ( patch( "homeassistant.components.update.UpdateEntity.async_install", side_effect=RuntimeError, ), pytest.raises(RuntimeError), ): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, {ATTR_ENTITY_ID: "update.update_available"}, blocking=True, ) await hass.async_block_till_done() assert len(events) == 2 assert events[0].data.get("old_state").attributes[ATTR_IN_PROGRESS] is False assert events[0].data.get("old_state").attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert events[0].data.get("new_state").attributes[ATTR_IN_PROGRESS] is True assert events[0].data.get("new_state").attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert events[1].data.get("old_state").attributes[ATTR_IN_PROGRESS] is True assert events[1].data.get("old_state").attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert events[1].data.get("new_state").attributes[ATTR_IN_PROGRESS] is False assert events[1].data.get("new_state").attributes[ATTR_INSTALLED_VERSION] == "1.0.0" async def test_restore_state( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity] ) -> None: """Test we restore skipped version state.""" mock_restore_cache( hass, ( State( "update.update_available", STATE_ON, # Incorrect, but helps checking if it is ignored { ATTR_SKIPPED_VERSION: "1.0.1", }, ), ), ) setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() state = hass.states.get("update.update_available") assert state assert state.state == STATE_OFF assert state.attributes[ATTR_INSTALLED_VERSION] == "1.0.0" assert state.attributes[ATTR_LATEST_VERSION] == "1.0.1" assert state.attributes[ATTR_SKIPPED_VERSION] == "1.0.1" async def test_release_notes( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], hass_ws_client: WebSocketGenerator, ) -> None: """Test getting the release notes over the websocket connection.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() client = await hass_ws_client(hass) await hass.async_block_till_done() await client.send_json( { "id": 1, "type": "update/release_notes", "entity_id": "update.update_with_release_notes", } ) result = await client.receive_json() assert result["result"] == "Release notes" async def test_release_notes_entity_not_found( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], hass_ws_client: WebSocketGenerator, ) -> None: """Test getting the release notes for not found entity.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() client = await hass_ws_client(hass) await hass.async_block_till_done() await client.send_json( { "id": 1, "type": "update/release_notes", "entity_id": "update.entity_not_found", } ) result = await client.receive_json() assert result["error"]["code"] == "not_found" assert result["error"]["message"] == "Entity not found" async def test_release_notes_entity_does_not_support_release_notes( hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity], hass_ws_client: WebSocketGenerator, ) -> None: """Test getting the release notes for entity that does not support release notes.""" setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() client = await hass_ws_client(hass) await hass.async_block_till_done() await client.send_json( { "id": 1, "type": "update/release_notes", "entity_id": "update.update_available", } ) result = await client.receive_json() assert result["error"]["code"] == "not_supported" assert result["error"]["message"] == "Entity does not support release notes" class MockFlow(ConfigFlow): """Test flow.""" @pytest.fixture(autouse=True) def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]: """Mock config flow.""" mock_platform(hass, f"{TEST_DOMAIN}.config_flow") with mock_config_flow(TEST_DOMAIN, MockFlow): yield async def test_name(hass: HomeAssistant) -> None: """Test update name.""" async def async_setup_entry_init( hass: HomeAssistant, config_entry: ConfigEntry ) -> bool: """Set up test config entry.""" await hass.config_entries.async_forward_entry_setup(config_entry, DOMAIN) return True mock_platform(hass, f"{TEST_DOMAIN}.config_flow") mock_integration( hass, MockModule( TEST_DOMAIN, async_setup_entry=async_setup_entry_init, ), ) # Unnamed update entity without device class -> no name entity1 = UpdateEntity() entity1.entity_id = "update.test1" # Unnamed update entity with device class but has_entity_name False -> no name entity2 = UpdateEntity() entity2.entity_id = "update.test2" entity2._attr_device_class = UpdateDeviceClass.FIRMWARE # Unnamed update entity with device class and has_entity_name True -> named entity3 = UpdateEntity() entity3.entity_id = "update.test3" entity3._attr_device_class = UpdateDeviceClass.FIRMWARE entity3._attr_has_entity_name = True # Unnamed update entity with device class and has_entity_name True -> named entity4 = UpdateEntity() entity4.entity_id = "update.test4" entity4.entity_description = UpdateEntityDescription( "test", UpdateDeviceClass.FIRMWARE, has_entity_name=True, ) async def async_setup_entry_platform( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up test update platform via config entry.""" async_add_entities([entity1, entity2, entity3, entity4]) mock_platform( hass, f"{TEST_DOMAIN}.{DOMAIN}", MockPlatform(async_setup_entry=async_setup_entry_platform), ) config_entry = MockConfigEntry(domain=TEST_DOMAIN) config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() state = hass.states.get(entity1.entity_id) assert state assert "device_class" not in state.attributes assert "friendly_name" not in state.attributes state = hass.states.get(entity2.entity_id) assert state assert state.attributes.get("device_class") == "firmware" assert "friendly_name" not in state.attributes expected = { "device_class": "firmware", "friendly_name": "Firmware", } state = hass.states.get(entity3.entity_id) assert state assert expected.items() <= state.attributes.items() state = hass.states.get(entity4.entity_id) assert state assert expected.items() <= state.attributes.items() def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None: """Test deprecated supported features ints.""" class MockUpdateEntity(UpdateEntity): @property def supported_features(self) -> int: """Return supported features.""" return 1 entity = MockUpdateEntity() assert entity.supported_features_compat is UpdateEntityFeature(1) assert "MockUpdateEntity" in caplog.text assert "is using deprecated supported features values" in caplog.text assert "Instead it should use" in caplog.text assert "UpdateEntityFeature.INSTALL" in caplog.text caplog.clear() assert entity.supported_features_compat is UpdateEntityFeature(1) assert "is using deprecated supported features values" not in caplog.text async def test_deprecated_supported_features_ints_with_service_call( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, ) -> None: """Test deprecated supported features ints with install service.""" async def async_setup_entry_init( hass: HomeAssistant, config_entry: ConfigEntry ) -> bool: """Set up test config entry.""" await hass.config_entries.async_forward_entry_setup(config_entry, DOMAIN) return True mock_platform(hass, f"{TEST_DOMAIN}.config_flow") mock_integration( hass, MockModule( TEST_DOMAIN, async_setup_entry=async_setup_entry_init, ), ) class MockUpdateEntity(UpdateEntity): _attr_supported_features = 1 | 2 def install(self, version: str | None = None, backup: bool = False) -> None: """Install an update.""" entity = MockUpdateEntity() entity.entity_id = ( "update.test_deprecated_supported_features_ints_with_service_call" ) async def async_setup_entry_platform( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up test update platform via config entry.""" async_add_entities([entity]) mock_platform( hass, f"{TEST_DOMAIN}.{DOMAIN}", MockPlatform(async_setup_entry=async_setup_entry_platform), ) config_entry = MockConfigEntry(domain=TEST_DOMAIN) config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert "is using deprecated supported features values" in caplog.text assert isinstance(entity.supported_features, int) with pytest.raises( HomeAssistantError, match="Backup is not supported for update.test_deprecated_supported_features_ints_with_service_call", ): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, { ATTR_VERSION: "0.9.9", ATTR_BACKUP: True, ATTR_ENTITY_ID: "update.test_deprecated_supported_features_ints_with_service_call", }, blocking=True, )