1
0
mirror of https://github.com/home-assistant/core.git synced 2026-06-01 21:24:17 +01:00
Files
core/tests/components/hassio/test_update.py
T
2026-05-17 13:09:56 +02:00

1985 lines
65 KiB
Python

"""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/<slug>/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"