"""The tests for the hassio update entities.""" from dataclasses import replace from datetime import datetime, timedelta import os from typing import Any from unittest.mock import AsyncMock, MagicMock, Mock, patch from uuid import uuid4 from aiohasupervisor import ( SupervisorBadRequestError, SupervisorError, SupervisorNotFoundError, ) from aiohasupervisor.models import ( AddonState, HomeAssistantUpdateOptions, InstalledAddonComplete, Job, JobsInfo, OSUpdate, StoreAddonUpdate, ) import pytest from homeassistant.components.backup import BackupManagerError, ManagerBackup # pylint: disable-next=home-assistant-component-root-import from homeassistant.components.backup.manager import AgentBackupStatus from homeassistant.components.hassio import DOMAIN from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY from homeassistant.const import __version__ as HAVERSION from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry, async_fire_time_changed from tests.typing import WebSocketGenerator MOCK_ENVIRON = {"SUPERVISOR": "127.0.0.1", "SUPERVISOR_TOKEN": "abcdefgh"} @pytest.fixture(autouse=True) def mock_all( addon_installed: AsyncMock, store_info: AsyncMock, addon_stats: AsyncMock, addon_changelog: AsyncMock, resolution_info: AsyncMock, jobs_info: AsyncMock, host_info: AsyncMock, supervisor_root_info: AsyncMock, homeassistant_info: AsyncMock, supervisor_info: AsyncMock, addons_list: AsyncMock, network_info: AsyncMock, os_info: AsyncMock, homeassistant_stats: AsyncMock, supervisor_stats: AsyncMock, ingress_panels: AsyncMock, ) -> None: """Mock all setup requests.""" homeassistant_info.return_value = replace( homeassistant_info.return_value, version="1.0.0dev221", version_latest="1.0.0dev222", update_available=True, ) os_info.return_value = replace( os_info.return_value, version="1.0.0dev2221", version_latest="1.0.0dev2222", update_available=True, ) supervisor_info.return_value = replace( supervisor_info.return_value, version_latest="1.0.1dev222", update_available=True, ) def mock_addon_info(slug: str): addon = Mock( spec=InstalledAddonComplete, to_dict=addon_installed.return_value.to_dict, **addon_installed.return_value.to_dict(), ) if slug == "test": addon.name = "test" addon.slug = "test" addon.version = "2.0.0" addon.version_latest = "2.0.1" addon.update_available = True addon.state = AddonState.STARTED addon.url = "https://github.com/home-assistant/addons/test" addon.auto_update = True else: addon.name = "test2" addon.slug = "test2" addon.version = "3.1.0" addon.version_latest = "3.1.0" addon.update_available = False addon.state = AddonState.STOPPED addon.url = "https://github.com" addon.auto_update = False return addon addon_installed.side_effect = mock_addon_info @pytest.mark.parametrize( ("entity_id", "expected_state", "auto_update"), [ ("update.home_assistant_operating_system_update", "on", False), ("update.home_assistant_supervisor_update", "on", True), ("update.home_assistant_core_update", "on", False), ("update.test_update", "on", True), ("update.test2_update", "off", False), ], ) async def test_update_entities( hass: HomeAssistant, entity_id, expected_state, auto_update, addon_installed: AsyncMock, ) -> None: """Test update entities.""" addon_installed.return_value.auto_update = auto_update config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() # Verify that the entity have the expected state. state = hass.states.get(entity_id) assert state.state == expected_state # Verify that the auto_update attribute is correct assert state.attributes["auto_update"] is auto_update async def test_update_addon(hass: HomeAssistant, update_addon: AsyncMock) -> None: """Test updating addon update entity.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() with patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", ) as mock_create_backup: await hass.services.async_call( "update", "install", {"entity_id": "update.test_update"}, blocking=True, ) mock_create_backup.assert_not_called() update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False)) async def test_update_addon_progress( hass: HomeAssistant, hass_supervisor_ws_client: WebSocketGenerator ) -> None: """Test progress reporting for addon update.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() client = await hass_supervisor_ws_client() message_id = 0 job_uuid = uuid4().hex def make_job_message(progress: float, done: bool | None): nonlocal message_id message_id += 1 return { "id": message_id, "type": "supervisor/event", "data": { "event": "job", "data": { "uuid": job_uuid, "created": "2025-09-29T00:00:00.000000+00:00", "name": "addon_manager_update", "reference": "test", "progress": progress, "done": done, "stage": None, "extra": {"total": 1234567890} if progress > 0 else None, "errors": [], }, }, } await client.send_json(make_job_message(progress=0, done=None)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert hass.states.get("update.test_update").attributes.get("in_progress") is False assert ( hass.states.get("update.test_update").attributes.get("update_percentage") is None ) await client.send_json(make_job_message(progress=5, done=False)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert hass.states.get("update.test_update").attributes.get("in_progress") is True assert ( hass.states.get("update.test_update").attributes.get("update_percentage") == 5 ) await client.send_json(make_job_message(progress=50, done=False)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert hass.states.get("update.test_update").attributes.get("in_progress") is True assert ( hass.states.get("update.test_update").attributes.get("update_percentage") == 50 ) await client.send_json(make_job_message(progress=100, done=True)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert hass.states.get("update.test_update").attributes.get("in_progress") is False assert ( hass.states.get("update.test_update").attributes.get("update_percentage") is None ) async def test_addon_update_progress_startup( hass: HomeAssistant, jobs_info: AsyncMock ) -> None: """Test addon update in progress during home assistant startup.""" jobs_info.return_value = JobsInfo( ignore_conditions=[], jobs=[ Job( name="addon_manager_update", reference="test", uuid=uuid4().hex, progress=50, stage=None, done=False, errors=[], created=datetime.now(), child_jobs=[], extra={"total": 1234567890}, ) ], ) config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() assert hass.states.get("update.test_update").attributes.get("in_progress") is True assert ( hass.states.get("update.test_update").attributes.get("update_percentage") == 50 ) async def setup_backup_integration(hass: HomeAssistant) -> None: """Set up the backup integration.""" assert await async_setup_component(hass, "backup", {}) await hass.async_block_till_done() @pytest.mark.parametrize( ("commands", "default_mount", "expected_kwargs"), [ ( [], None, { "agent_ids": ["hassio.local"], "extra_metadata": {"supervisor.addon_update": "test"}, "include_addons": ["test"], "include_all_addons": False, "include_database": False, "include_folders": None, "include_homeassistant": False, "name": "test 2.0.0", "password": None, }, ), ( [], "my_nas", { "agent_ids": ["hassio.my_nas"], "extra_metadata": {"supervisor.addon_update": "test"}, "include_addons": ["test"], "include_all_addons": False, "include_database": False, "include_folders": None, "include_homeassistant": False, "name": "test 2.0.0", "password": None, }, ), ( [ { "type": "backup/config/update", "create_backup": { "agent_ids": ["test-agent"], "include_addons": ["my-addon"], "include_all_addons": True, "include_database": False, "include_folders": ["share"], "name": "cool_backup", "password": "hunter2", }, }, ], None, { "agent_ids": ["hassio.local"], "extra_metadata": {"supervisor.addon_update": "test"}, "include_addons": ["test"], "include_all_addons": False, "include_database": False, "include_folders": None, "include_homeassistant": False, "name": "test 2.0.0", "password": "hunter2", }, ), ], ) async def test_update_addon_with_backup( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, supervisor_client: AsyncMock, update_addon: AsyncMock, commands: list[dict[str, Any]], default_mount: str | None, expected_kwargs: dict[str, Any], ) -> None: """Test updating addon update entity.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await setup_backup_integration(hass) client = await hass_ws_client(hass) for command in commands: await client.send_json_auto_id(command) result = await client.receive_json() assert result["success"] supervisor_client.mounts.info.return_value.default_backup_mount = default_mount with patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", ) as mock_create_backup: await hass.services.async_call( "update", "install", {"entity_id": "update.test_update", "backup": True}, blocking=True, ) mock_create_backup.assert_called_once_with(**expected_kwargs) update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False)) @pytest.mark.parametrize( ("backups", "removed_backups"), [ ( {}, [], ), ( { "backup-1": MagicMock( agents={"hassio.local": MagicMock(spec=AgentBackupStatus)}, date="2024-11-10T04:45:00+01:00", with_automatic_settings=True, spec=ManagerBackup, ), "backup-2": MagicMock( agents={"hassio.local": MagicMock(spec=AgentBackupStatus)}, date="2024-11-11T04:45:00+01:00", with_automatic_settings=False, spec=ManagerBackup, ), "backup-3": MagicMock( agents={"hassio.local": MagicMock(spec=AgentBackupStatus)}, date="2024-11-11T04:45:00+01:00", extra_metadata={"supervisor.addon_update": "other"}, with_automatic_settings=True, spec=ManagerBackup, ), "backup-4": MagicMock( agents={"hassio.local": MagicMock(spec=AgentBackupStatus)}, date="2024-11-11T04:45:00+01:00", extra_metadata={"supervisor.addon_update": "other"}, with_automatic_settings=True, spec=ManagerBackup, ), "backup-5": MagicMock( agents={"hassio.local": MagicMock(spec=AgentBackupStatus)}, date="2024-11-11T04:45:00+01:00", extra_metadata={"supervisor.addon_update": "test"}, with_automatic_settings=True, spec=ManagerBackup, ), "backup-6": MagicMock( agents={"hassio.local": MagicMock(spec=AgentBackupStatus)}, date="2024-11-12T04:45:00+01:00", extra_metadata={"supervisor.addon_update": "test"}, with_automatic_settings=True, spec=ManagerBackup, ), }, ["backup-5"], ), ], ) async def test_update_addon_with_backup_removes_old_backups( hass: HomeAssistant, supervisor_client: AsyncMock, update_addon: AsyncMock, backups: dict[str, ManagerBackup], removed_backups: list[str], ) -> None: """Test updating addon update entity.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await setup_backup_integration(hass) supervisor_client.mounts.info.return_value.default_backup_mount = None with ( patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", ) as mock_create_backup, patch( "homeassistant.components.backup.manager.BackupManager.async_delete_backup", autospec=True, return_value={}, ) as async_delete_backup, patch( "homeassistant.components.backup.manager.BackupManager.async_get_backups", return_value=(backups, {}), ), ): await hass.services.async_call( "update", "install", {"entity_id": "update.test_update", "backup": True}, blocking=True, ) mock_create_backup.assert_called_once_with( agent_ids=["hassio.local"], extra_metadata={"supervisor.addon_update": "test"}, include_addons=["test"], include_all_addons=False, include_database=False, include_folders=None, include_homeassistant=False, name="test 2.0.0", password=None, ) assert len(async_delete_backup.mock_calls) == len(removed_backups) for call in async_delete_backup.mock_calls: assert call.args[1] in removed_backups update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False)) async def test_update_os(hass: HomeAssistant, supervisor_client: AsyncMock) -> None: """Test updating OS update entity.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() supervisor_client.os.update.return_value = None with patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", ) as mock_create_backup: await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_operating_system_update"}, blocking=True, ) mock_create_backup.assert_not_called() supervisor_client.os.update.assert_called_once_with(OSUpdate(version=None)) @pytest.mark.parametrize( ("commands", "default_mount", "expected_kwargs"), [ ( [], None, { "agent_ids": ["hassio.local"], "include_addons": None, "include_all_addons": False, "include_database": True, "include_folders": None, "include_homeassistant": True, "name": f"Home Assistant Core {HAVERSION}", "password": None, }, ), ( [], "my_nas", { "agent_ids": ["hassio.my_nas"], "include_addons": None, "include_all_addons": False, "include_database": True, "include_folders": None, "include_homeassistant": True, "name": f"Home Assistant Core {HAVERSION}", "password": None, }, ), ( [ { "type": "backup/config/update", "create_backup": { "agent_ids": ["test-agent"], "include_addons": ["my-addon"], "include_all_addons": True, "include_database": False, "include_folders": ["share"], "name": "cool_backup", "password": "hunter2", }, }, ], None, { "agent_ids": ["test-agent"], "include_addons": ["my-addon"], "include_all_addons": True, "include_database": False, "include_folders": ["share"], "include_homeassistant": True, "name": "cool_backup", "password": "hunter2", "with_automatic_settings": True, }, ), ], ) async def test_update_os_with_backup( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, supervisor_client: AsyncMock, commands: list[dict[str, Any]], default_mount: str | None, expected_kwargs: dict[str, Any], ) -> None: """Test updating OS update entity.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await setup_backup_integration(hass) client = await hass_ws_client(hass) for command in commands: await client.send_json_auto_id(command) result = await client.receive_json() assert result["success"] supervisor_client.os.update.return_value = None supervisor_client.mounts.info.return_value.default_backup_mount = default_mount with patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", ) as mock_create_backup: await hass.services.async_call( "update", "install", { "entity_id": "update.home_assistant_operating_system_update", "backup": True, }, blocking=True, ) mock_create_backup.assert_called_once_with(**expected_kwargs) supervisor_client.os.update.assert_called_once_with(OSUpdate(version=None)) async def test_update_core(hass: HomeAssistant, supervisor_client: AsyncMock) -> None: """Test updating core update entity.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() supervisor_client.homeassistant.update.return_value = None with patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", ) as mock_create_backup: await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_core_update"}, blocking=True, ) mock_create_backup.assert_not_called() supervisor_client.homeassistant.update.assert_called_once_with( HomeAssistantUpdateOptions(version=None, backup=False) ) async def test_update_core_progress( hass: HomeAssistant, hass_supervisor_ws_client: WebSocketGenerator ) -> None: """Test progress reporting for core update.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() client = await hass_supervisor_ws_client() message_id = 0 job_uuid = uuid4().hex def make_job_message( progress: float, done: bool | None, errors: list[dict[str, str]] | None = None ): nonlocal message_id message_id += 1 return { "id": message_id, "type": "supervisor/event", "data": { "event": "job", "data": { "uuid": job_uuid, "created": "2025-09-29T00:00:00.000000+00:00", "name": "home_assistant_core_update", "reference": None, "progress": progress, "done": done, "stage": None, "extra": {"total": 1234567890} if progress > 0 else None, "errors": errors or [], }, }, } await client.send_json(make_job_message(progress=0, done=None)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "in_progress" ) is False ) assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "update_percentage" ) is None ) await client.send_json(make_job_message(progress=5, done=False)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "in_progress" ) is True ) assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "update_percentage" ) == 5 ) await client.send_json(make_job_message(progress=50, done=False)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "in_progress" ) is True ) assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "update_percentage" ) == 50 ) # During a successful update Home Assistant is stopped before the update job # reaches the end. An error ends it early so we use that for test await client.send_json( make_job_message( progress=70, done=True, errors=[ {"type": "HomeAssistantUpdateError", "message": "bad", "stage": None} ], ) ) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "in_progress" ) is False ) assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "update_percentage" ) is None ) async def test_core_update_progress_startup( hass: HomeAssistant, jobs_info: AsyncMock ) -> None: """Test core update in progress during home assistant startup. This is an odd test, it's very unlikely core will be starting during an update. It is technically possible though as core isn't stopped until the docker portion is complete and updates can be started from CLI. """ jobs_info.return_value = JobsInfo( ignore_conditions=[], jobs=[ Job( name="home_assistant_core_update", reference=None, uuid=uuid4().hex, progress=50, stage=None, done=False, errors=[], created=datetime.now(), child_jobs=[], extra={"total": 1234567890}, ) ], ) config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "in_progress" ) is True ) assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "update_percentage" ) == 50 ) @pytest.mark.parametrize( ("commands", "default_mount", "expected_kwargs"), [ ( [], None, { "agent_ids": ["hassio.local"], "include_addons": None, "include_all_addons": False, "include_database": True, "include_folders": None, "include_homeassistant": True, "name": f"Home Assistant Core {HAVERSION}", "password": None, }, ), ( [], "my_nas", { "agent_ids": ["hassio.my_nas"], "include_addons": None, "include_all_addons": False, "include_database": True, "include_folders": None, "include_homeassistant": True, "name": f"Home Assistant Core {HAVERSION}", "password": None, }, ), ( [ { "type": "backup/config/update", "create_backup": { "agent_ids": ["test-agent"], "include_addons": ["my-addon"], "include_all_addons": True, "include_database": False, "include_folders": ["share"], "name": "cool_backup", "password": "hunter2", }, }, ], None, { "agent_ids": ["test-agent"], "include_addons": ["my-addon"], "include_all_addons": True, "include_database": False, "include_folders": ["share"], "include_homeassistant": True, "name": "cool_backup", "password": "hunter2", "with_automatic_settings": True, }, ), ], ) async def test_update_core_with_backup( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, supervisor_client: AsyncMock, commands: list[dict[str, Any]], default_mount: str | None, expected_kwargs: dict[str, Any], ) -> None: """Test updating core update entity.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await setup_backup_integration(hass) client = await hass_ws_client(hass) for command in commands: await client.send_json_auto_id(command) result = await client.receive_json() assert result["success"] supervisor_client.homeassistant.update.return_value = None supervisor_client.mounts.info.return_value.default_backup_mount = default_mount with patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", ) as mock_create_backup: await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_core_update", "backup": True}, blocking=True, ) mock_create_backup.assert_called_once_with(**expected_kwargs) supervisor_client.homeassistant.update.assert_called_once_with( HomeAssistantUpdateOptions(version=None, backup=False) ) async def test_update_core_sets_progress_immediately( hass: HomeAssistant, supervisor_client: AsyncMock ) -> None: """Test core update sets in_progress immediately when install starts.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() state = hass.states.get("update.home_assistant_core_update") assert state.attributes.get("in_progress") is False # Mock update_core to verify in_progress is set before it's called async def check_progress( hass: HomeAssistant, version: str | None, backup: bool ) -> None: assert ( hass.states.get("update.home_assistant_core_update").attributes.get( "in_progress" ) is True ) with patch( "homeassistant.components.hassio.update.update_core", side_effect=check_progress, ) as mock_update: await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_core_update", "backup": True}, blocking=True, ) mock_update.assert_called_once() async def test_update_core_resets_progress_on_error( hass: HomeAssistant, supervisor_client: AsyncMock ) -> None: """Test core update resets in_progress to False when update fails.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() state = hass.states.get("update.home_assistant_core_update") assert state.attributes.get("in_progress") is False with ( patch( "homeassistant.components.hassio.update.update_core", side_effect=HomeAssistantError, ), pytest.raises(HomeAssistantError), ): await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_core_update", "backup": True}, blocking=True, ) state = hass.states.get("update.home_assistant_core_update") assert state.attributes.get("in_progress") is False, ( "in_progress should be reset to False after error" ) async def test_update_addon_sets_progress_immediately( hass: HomeAssistant, supervisor_client: AsyncMock ) -> None: """Test addon update sets in_progress immediately when install starts.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() state = hass.states.get("update.test_update") assert state.attributes.get("in_progress") is False # Mock update_addon to verify in_progress is set before it's called async def check_progress( hass: HomeAssistant, addon: str, backup: bool, addon_name: str | None, installed_version: str | None, ) -> None: assert ( hass.states.get("update.test_update").attributes.get("in_progress") is True ) with patch( "homeassistant.components.hassio.update.update_addon", side_effect=check_progress, ) as mock_update: await hass.services.async_call( "update", "install", {"entity_id": "update.test_update", "backup": True}, blocking=True, ) mock_update.assert_called_once() async def test_update_addon_resets_progress_on_error( hass: HomeAssistant, hass_supervisor_ws_client: WebSocketGenerator, supervisor_client: AsyncMock, ) -> None: """Test addon update resets in_progress and update_percentage on failure.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() state = hass.states.get("update.test_update") assert state.attributes.get("in_progress") is False assert state.attributes.get("update_percentage") is None ws = await hass_supervisor_ws_client() job_uuid = uuid4().hex async def fake_update_addon_error( _hass: HomeAssistant, _addon: str, _backup: bool, _addon_name: str | None, _installed_version: str | None, ) -> None: """Report some progress, then fail - as a mid-pull network error would.""" await ws.send_json( { "id": 1, "type": "supervisor/event", "data": { "event": "job", "data": { "uuid": job_uuid, "created": "2025-09-29T00:00:00.000000+00:00", "name": "addon_manager_update", "reference": "test", "progress": 42, "done": False, "stage": None, "extra": {"total": 1234567890}, "errors": [], }, }, } ) msg = await ws.receive_json() assert msg["success"] await hass.async_block_till_done() raise HomeAssistantError with ( patch( "homeassistant.components.hassio.update.update_addon", side_effect=fake_update_addon_error, ), pytest.raises(HomeAssistantError), ): await hass.services.async_call( "update", "install", {"entity_id": "update.test_update", "backup": True}, blocking=True, ) state = hass.states.get("update.test_update") assert state.attributes.get("in_progress") is False, ( "in_progress should be reset to False after error" ) assert state.attributes.get("update_percentage") is None, ( "update_percentage should be reset to None after error" ) def _bump_addon_to( addons_list: AsyncMock, addon_installed: AsyncMock, version: str, version_latest: str, ) -> None: """Rewrite the addon fixtures to report a post-update version.""" current = addons_list.return_value addons_list.return_value = [ replace( current[0], version=version, version_latest=version_latest, update_available=version != version_latest, ), *current[1:], ] def _updated_info(slug: str): addon = Mock( spec=InstalledAddonComplete, to_dict=addon_installed.return_value.to_dict, **addon_installed.return_value.to_dict(), ) addon.name = "test" addon.slug = "test" addon.version = version addon.version_latest = version_latest addon.update_available = version != version_latest addon.state = AddonState.STARTED addon.url = "https://github.com/home-assistant/addons/test" addon.auto_update = True return addon addon_installed.side_effect = _updated_info async def test_update_addon_stays_in_progress_until_refresh( hass: HomeAssistant, hass_supervisor_ws_client: WebSocketGenerator, update_addon: AsyncMock, addon_installed: AsyncMock, addons_list: AsyncMock, ) -> None: """Test addon update entity stays in progress until coordinator refresh. Supervisor emits the ``addon_manager_update`` job ``done=True`` WS event a few milliseconds before ``/store/addons//update`` returns. Without the ``_update_ongoing`` guard, ``_attr_in_progress`` is cleared while the coordinator still holds the pre-update version and the UI briefly flips back to "Update available". """ config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() entity_id = "update.test_update" assert hass.states.get(entity_id).state == "on" ws = await hass_supervisor_ws_client() job_uuid = uuid4().hex in_progress_after_done: list[bool | None] = [] async def fake_update_addon(slug: str, _options: StoreAddonUpdate) -> None: """Mimic Supervisor: fire done=True on WS, then return HTTP response.""" await ws.send_json( { "id": 1, "type": "supervisor/event", "data": { "event": "job", "data": { "uuid": job_uuid, "created": "2025-09-29T00:00:00.000000+00:00", "name": "addon_manager_update", "reference": "test", "progress": 100, "done": True, "stage": None, "extra": {"total": 1234567890}, "errors": [], }, }, } ) msg = await ws.receive_json() assert msg["success"] await hass.async_block_till_done() in_progress_after_done.append( hass.states.get(entity_id).attributes.get("in_progress") ) _bump_addon_to(addons_list, addon_installed, "2.0.1", "2.0.1") update_addon.side_effect = fake_update_addon await hass.services.async_call( "update", "install", {"entity_id": entity_id}, blocking=True ) # The done=True WS event fired mid-install must not drop in_progress; the # coordinator data at that instant still carries the pre-update version. assert in_progress_after_done == [True] state = hass.states.get(entity_id) assert state.attributes.get("in_progress") is False assert state.state == "off" async def test_update_addon_completes_on_any_version_change( hass: HomeAssistant, update_addon: AsyncMock, addon_installed: AsyncMock, addons_list: AsyncMock, ) -> None: """Test completion when installed version changes from the pre-install one. If a newer upstream release appears between install start and the refresh, ``installed_version`` will not equal ``latest_version`` but will differ from the pre-install version. The ongoing flag must still clear. """ config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() entity_id = "update.test_update" async def fake_update_addon(slug: str, _options: StoreAddonUpdate) -> None: _bump_addon_to(addons_list, addon_installed, "2.0.1", "2.0.2") update_addon.side_effect = fake_update_addon await hass.services.async_call( "update", "install", {"entity_id": entity_id}, blocking=True ) state = hass.states.get(entity_id) assert state.attributes.get("in_progress") is False assert state.state == "on" async def test_update_supervisor( hass: HomeAssistant, supervisor_client: AsyncMock ) -> None: """Test updating supervisor update entity.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() supervisor_client.supervisor.update.return_value = None await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_supervisor_update"}, blocking=True, ) supervisor_client.supervisor.update.assert_called_once() async def test_update_supervisor_progress( hass: HomeAssistant, hass_supervisor_ws_client: WebSocketGenerator, supervisor_info: AsyncMock, ) -> None: """Test progress reporting for a Supervisor update not initiated via entity. Covers CLI-triggered and Supervisor self-update flows: the entity must show download progress from job events and stay in the installing state across the Supervisor restart until the coordinator observes the new installed version. """ config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() client = await hass_supervisor_ws_client() message_id = 0 job_uuid = uuid4().hex entity_id = "update.home_assistant_supervisor_update" def make_job_message(progress: float, done: bool | None) -> dict[str, Any]: nonlocal message_id message_id += 1 return { "id": message_id, "type": "supervisor/event", "data": { "event": "job", "data": { "uuid": job_uuid, "created": "2025-09-29T00:00:00.000000+00:00", "name": "supervisor_update", "reference": None, "progress": progress, "done": done, "stage": None, "extra": {"total": 1234567890} if progress > 0 else None, "errors": [], }, }, } await client.send_json(make_job_message(progress=0, done=None)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert hass.states.get(entity_id).attributes.get("in_progress") is False assert hass.states.get(entity_id).attributes.get("update_percentage") is None await client.send_json(make_job_message(progress=5, done=False)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert hass.states.get(entity_id).attributes.get("in_progress") is True assert hass.states.get(entity_id).attributes.get("update_percentage") == 5 await client.send_json(make_job_message(progress=50, done=False)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert hass.states.get(entity_id).attributes.get("update_percentage") == 50 # Job done: download finished, Supervisor is about to restart. The entity # must stay in the installing state until the new version is observed. await client.send_json(make_job_message(progress=100, done=True)) msg = await client.receive_json() assert msg["success"] await hass.async_block_till_done() assert hass.states.get(entity_id).attributes.get("in_progress") is True assert hass.states.get(entity_id).attributes.get("update_percentage") is None # New Supervisor comes up and fires STARTUP_COMPLETE. supervisor_info.return_value = replace( supervisor_info.return_value, version="1.0.1dev222", update_available=False, ) await client.send_json( { "id": message_id + 1, "type": "supervisor/event", "data": { "event": "supervisor_update", "update_key": "supervisor", "data": {"startup": "complete"}, }, } ) msg = await client.receive_json() assert msg["success"] async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=REQUEST_REFRESH_DELAY + 1) ) await hass.async_block_till_done() assert hass.states.get(entity_id).attributes.get("in_progress") is False async def test_update_supervisor_stays_in_progress_until_restart( hass: HomeAssistant, hass_supervisor_ws_client: WebSocketGenerator, supervisor_client: AsyncMock, supervisor_info: AsyncMock, ) -> None: """Test in_progress stays True after install returns, until Supervisor restart.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() entity_id = "update.home_assistant_supervisor_update" assert hass.states.get(entity_id).attributes.get("in_progress") is False supervisor_client.supervisor.update.return_value = None await hass.services.async_call( "update", "install", {"entity_id": entity_id}, blocking=True ) # The install HTTP call returned, but Supervisor is still restarting. # The base UpdateEntity reset _attr_in_progress; # _update_ongoing keeps us in progress. assert hass.states.get(entity_id).attributes.get("in_progress") is True # Supervisor comes up with the new version and fires STARTUP_COMPLETE. supervisor_info.return_value = replace( supervisor_info.return_value, version="1.0.1dev222", update_available=False, ) client = await hass_supervisor_ws_client() await client.send_json( { "id": 1, "type": "supervisor/event", "data": { "event": "supervisor_update", "update_key": "supervisor", "data": {"startup": "complete"}, }, } ) msg = await client.receive_json() assert msg["success"] async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=REQUEST_REFRESH_DELAY + 1) ) await hass.async_block_till_done() assert hass.states.get(entity_id).attributes.get("in_progress") is False async def test_update_supervisor_completes_on_any_version_change( hass: HomeAssistant, hass_supervisor_ws_client: WebSocketGenerator, supervisor_client: AsyncMock, supervisor_info: AsyncMock, ) -> None: """Test completion is detected when installed version changes. If upstream publishes an even newer release between install-start and the post-restart refresh, installed_version will not equal latest_version but will differ from the pre-install version. _update_ongoing must still clear. """ config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() entity_id = "update.home_assistant_supervisor_update" supervisor_client.supervisor.update.return_value = None await hass.services.async_call( "update", "install", {"entity_id": entity_id}, blocking=True ) assert hass.states.get(entity_id).attributes.get("in_progress") is True supervisor_info.return_value = replace( supervisor_info.return_value, version="1.0.1dev222", version_latest="1.0.2dev223", update_available=True, ) client = await hass_supervisor_ws_client() await client.send_json( { "id": 1, "type": "supervisor/event", "data": { "event": "supervisor_update", "update_key": "supervisor", "data": {"startup": "complete"}, }, } ) msg = await client.receive_json() assert msg["success"] async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=REQUEST_REFRESH_DELAY + 1) ) await hass.async_block_till_done() assert hass.states.get(entity_id).attributes.get("in_progress") is False async def test_update_addon_with_error( hass: HomeAssistant, update_addon: AsyncMock, ) -> None: """Test updating addon update entity with error.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() update_addon.side_effect = SupervisorError with pytest.raises(HomeAssistantError, match=r"^Error updating test:"): await hass.services.async_call( "update", "install", {"entity_id": "update.test_update"}, blocking=True, ) @pytest.mark.parametrize( ("create_backup_error", "delete_filtered_backups_error", "message"), [ (BackupManagerError, None, r"^Error creating backup: "), (None, BackupManagerError, r"^Error deleting old backups: "), ], ) async def test_update_addon_with_backup_and_error( hass: HomeAssistant, supervisor_client: AsyncMock, create_backup_error: Exception | None, delete_filtered_backups_error: Exception | None, message: str, ) -> None: """Test updating addon update entity with error.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await setup_backup_integration(hass) supervisor_client.homeassistant.update.return_value = None supervisor_client.mounts.info.return_value.default_backup_mount = None with ( patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", side_effect=create_backup_error, ), patch( "homeassistant.components.backup.manager.BackupManager.async_delete_filtered_backups", side_effect=delete_filtered_backups_error, ), pytest.raises(HomeAssistantError, match=message), ): await hass.services.async_call( "update", "install", {"entity_id": "update.test_update", "backup": True}, blocking=True, ) async def test_update_os_with_error( hass: HomeAssistant, supervisor_client: AsyncMock ) -> None: """Test updating OS update entity with error.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() supervisor_client.os.update.side_effect = SupervisorError with pytest.raises( HomeAssistantError, match=r"^Error updating Home Assistant Operating System:" ): await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_operating_system_update"}, blocking=True, ) async def test_update_os_with_backup_and_error( hass: HomeAssistant, supervisor_client: AsyncMock, ) -> None: """Test updating OS update entity with error.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await setup_backup_integration(hass) supervisor_client.os.update.return_value = None supervisor_client.mounts.info.return_value.default_backup_mount = None with ( patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", side_effect=BackupManagerError, ), pytest.raises(HomeAssistantError, match=r"^Error creating backup:"), ): await hass.services.async_call( "update", "install", { "entity_id": "update.home_assistant_operating_system_update", "backup": True, }, blocking=True, ) async def test_update_supervisor_with_error( hass: HomeAssistant, supervisor_client: AsyncMock ) -> None: """Test updating supervisor update entity with error.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() supervisor_client.supervisor.update.side_effect = SupervisorError with pytest.raises( HomeAssistantError, match=r"^Error updating Home Assistant Supervisor:" ): await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_supervisor_update"}, blocking=True, ) async def test_update_core_with_error( hass: HomeAssistant, supervisor_client: AsyncMock ) -> None: """Test updating core update entity with error.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): assert await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() supervisor_client.homeassistant.update.side_effect = SupervisorError with pytest.raises( HomeAssistantError, match=r"^Error updating Home Assistant Core:" ): await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_core_update"}, blocking=True, ) async def test_update_core_with_backup_and_error( hass: HomeAssistant, supervisor_client: AsyncMock, ) -> None: """Test updating core update entity with error.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await setup_backup_integration(hass) supervisor_client.homeassistant.update.return_value = None supervisor_client.mounts.info.return_value.default_backup_mount = None with ( patch( "homeassistant.components.backup.manager.BackupManager.async_create_backup", side_effect=BackupManagerError, ), pytest.raises(HomeAssistantError, match=r"^Error creating backup:"), ): await hass.services.async_call( "update", "install", {"entity_id": "update.home_assistant_core_update", "backup": True}, blocking=True, ) async def test_release_notes_between_versions( hass: HomeAssistant, addon_changelog: AsyncMock, hass_ws_client: WebSocketGenerator ) -> None: """Test release notes between versions.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) addon_changelog.return_value = "# 2.0.1\nNew updates\n# 2.0.0\nOld updates" with ( patch.dict(os.environ, MOCK_ENVIRON), ): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result 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.test_update", } ) result = await client.receive_json() assert "Old updates" not in result["result"] assert "New updates" in result["result"] async def test_release_notes_full( hass: HomeAssistant, addon_changelog: AsyncMock, hass_ws_client: WebSocketGenerator ) -> None: """Test release notes no match.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) full_changelog = "# 2.0.0\nNew updates\n# 2.0.0\nOld updates" addon_changelog.return_value = full_changelog with ( patch.dict(os.environ, MOCK_ENVIRON), ): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result 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.test_update", } ) result = await client.receive_json() assert "Old updates" in result["result"] assert "New updates" in result["result"] # Update entity without update should returns full changelog await client.send_json( { "id": 2, "type": "update/release_notes", "entity_id": "update.test2_update", } ) result = await client.receive_json() assert result["result"] == full_changelog async def test_not_release_notes( hass: HomeAssistant, addon_changelog: AsyncMock, hass_ws_client: WebSocketGenerator ) -> None: """Test handling where there are no release notes.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) addon_changelog.side_effect = SupervisorNotFoundError() with ( patch.dict(os.environ, MOCK_ENVIRON), ): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result 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.test_update", } ) result = await client.receive_json() assert result["result"] is None async def test_no_os_entity( hass: HomeAssistant, supervisor_root_info: AsyncMock ) -> None: """Test handling where there is no os entity.""" supervisor_root_info.return_value = replace( supervisor_root_info.return_value, hassos=None ) with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) assert result await hass.async_block_till_done() # Verify that the entity does not exist assert not hass.states.get("update.home_assistant_operating_system_update") async def test_setting_up_core_update_when_addon_fails( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, addon_installed: AsyncMock, addon_stats: AsyncMock, addon_changelog: AsyncMock, ) -> None: """Test setting up core update when single addon fails.""" addon_installed.side_effect = SupervisorBadRequestError("Addon Test does not exist") addon_stats.side_effect = SupervisorBadRequestError("add-on is not running") addon_changelog.side_effect = SupervisorBadRequestError("add-on is not running") with patch.dict(os.environ, MOCK_ENVIRON): result = await async_setup_component( hass, "hassio", {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, ) await hass.async_block_till_done() assert result # There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer async_fire_time_changed( hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY) ) await hass.async_block_till_done() # Verify that the core update entity does exist state = hass.states.get("update.home_assistant_core_update") assert state assert state.state == "on"