mirror of
https://github.com/home-assistant/core.git
synced 2026-04-02 00:20:30 +01:00
1534 lines
52 KiB
Python
1534 lines
52 KiB
Python
"""The tests for the hassio component."""
|
|
|
|
from dataclasses import replace
|
|
from datetime import timedelta
|
|
import os
|
|
from pathlib import PurePath
|
|
from typing import Any
|
|
from unittest.mock import ANY, AsyncMock, Mock, call, patch
|
|
from uuid import uuid4
|
|
|
|
from aiohasupervisor import SupervisorError
|
|
from aiohasupervisor.models import (
|
|
AddonsStats,
|
|
AddonStage,
|
|
AddonState,
|
|
CIFSMountResponse,
|
|
FullBackupOptions,
|
|
HomeAssistantOptions,
|
|
InstalledAddon,
|
|
InstalledAddonComplete,
|
|
MountsInfo,
|
|
MountState,
|
|
MountType,
|
|
MountUsage,
|
|
NewBackup,
|
|
PartialBackupOptions,
|
|
PartialRestoreOptions,
|
|
SupervisorOptions,
|
|
)
|
|
from freezegun.api import FrozenDateTimeFactory
|
|
import pytest
|
|
from voluptuous import Invalid
|
|
|
|
from homeassistant.auth.const import GROUP_ID_ADMIN
|
|
from homeassistant.components import frontend, hassio
|
|
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
|
from homeassistant.components.hassio import (
|
|
ADDONS_COORDINATOR,
|
|
DOMAIN,
|
|
get_core_info,
|
|
hostname_from_addon_slug,
|
|
)
|
|
from homeassistant.components.hassio.config import STORAGE_KEY
|
|
from homeassistant.components.hassio.const import (
|
|
HASSIO_UPDATE_INTERVAL,
|
|
REQUEST_REFRESH_DELAY,
|
|
)
|
|
from homeassistant.components.homeassistant import (
|
|
DOMAIN as HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
)
|
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
|
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
|
from homeassistant.helpers.hassio import is_hassio
|
|
from homeassistant.setup import async_setup_component
|
|
from homeassistant.util import dt as dt_util
|
|
from homeassistant.util.yaml import load_yaml_dict
|
|
|
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
|
|
|
MOCK_ENVIRON = {"SUPERVISOR": "127.0.0.1", "SUPERVISOR_TOKEN": "abcdefgh"}
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_all(
|
|
store_info: AsyncMock,
|
|
addon_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,
|
|
addon_installed: AsyncMock,
|
|
ingress_panels: AsyncMock,
|
|
) -> None:
|
|
"""Mock all setup requests."""
|
|
addons_list.return_value[0] = replace(
|
|
addons_list.return_value[0],
|
|
version="1.0.0",
|
|
version_latest="1.0.0",
|
|
update_available=False,
|
|
state=AddonState.STOPPED,
|
|
)
|
|
addons_list.return_value[1] = replace(
|
|
addons_list.return_value[1],
|
|
version="1.0.0",
|
|
version_latest="1.0.0",
|
|
)
|
|
addon_installed.return_value.state = AddonState.STOPPED
|
|
|
|
async def mock_addon_stats(addon: str) -> AddonsStats:
|
|
"""Mock addon stats for test and test2."""
|
|
if addon in {"test2", "test3"}:
|
|
return AddonsStats(
|
|
cpu_percent=0.8,
|
|
memory_usage=51941376,
|
|
memory_limit=3977146368,
|
|
memory_percent=1.31,
|
|
network_rx=31338284,
|
|
network_tx=15692900,
|
|
blk_read=740077568,
|
|
blk_write=6004736,
|
|
)
|
|
return AddonsStats(
|
|
cpu_percent=0.99,
|
|
memory_usage=182611968,
|
|
memory_limit=3977146368,
|
|
memory_percent=4.59,
|
|
network_rx=362570232,
|
|
network_tx=82374138,
|
|
blk_read=46010945536,
|
|
blk_write=15051526144,
|
|
)
|
|
|
|
addon_stats.side_effect = mock_addon_stats
|
|
|
|
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.url = "https://github.com/home-assistant/addons/test"
|
|
addon.auto_update = True
|
|
else:
|
|
addon.name = "test2"
|
|
addon.slug = "test2"
|
|
addon.url = "https://github.com"
|
|
addon.auto_update = False
|
|
|
|
return addon
|
|
|
|
addon_info.side_effect = mock_addon_info
|
|
|
|
|
|
async def test_setup_api_ping(
|
|
hass: HomeAssistant, supervisor_client: AsyncMock
|
|
) -> None:
|
|
"""Test setup with API ping."""
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
assert get_core_info(hass)["version_latest"] == "1.0.0"
|
|
assert is_hassio(hass)
|
|
|
|
|
|
async def test_setup_api_panel(hass: HomeAssistant) -> None:
|
|
"""Test setup with API ping."""
|
|
assert await async_setup_component(hass, "frontend", {})
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {})
|
|
assert result
|
|
|
|
panels = hass.data[frontend.DATA_PANELS]
|
|
|
|
assert panels.get("hassio").to_response() == {
|
|
"component_name": "custom",
|
|
"icon": None,
|
|
"title": None,
|
|
"default_visible": True,
|
|
"config": {
|
|
"_panel_custom": {
|
|
"embed_iframe": True,
|
|
"js_url": "/api/hassio/app/entrypoint.js",
|
|
"name": "hassio-main",
|
|
"trust_external": False,
|
|
}
|
|
},
|
|
"url_path": "hassio",
|
|
"require_admin": True,
|
|
"show_in_sidebar": True,
|
|
"config_panel_domain": None,
|
|
}
|
|
|
|
|
|
async def test_setup_app_panel(hass: HomeAssistant) -> None:
|
|
"""Test app panel is registered."""
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {})
|
|
await hass.async_block_till_done()
|
|
assert result
|
|
|
|
panels = hass.data[frontend.DATA_PANELS]
|
|
|
|
assert panels.get("app").to_response() == {
|
|
"component_name": "app",
|
|
"icon": None,
|
|
"title": None,
|
|
"default_visible": True,
|
|
"config": None,
|
|
"url_path": "app",
|
|
"require_admin": False,
|
|
"show_in_sidebar": True,
|
|
"config_panel_domain": None,
|
|
}
|
|
|
|
|
|
async def test_setup_api_push_api_data(
|
|
hass: HomeAssistant, supervisor_client: AsyncMock
|
|
) -> None:
|
|
"""Test setup with API push."""
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(
|
|
hass, "hassio", {"http": {"server_port": 9999}, "hassio": {}}
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
assert result
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
supervisor_client.homeassistant.set_options.assert_called_once_with(
|
|
HomeAssistantOptions(ssl=False, port=9999, refresh_token=ANY)
|
|
)
|
|
|
|
|
|
async def test_setup_api_push_api_data_error(
|
|
hass: HomeAssistant, supervisor_client: AsyncMock, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""Test setup with error while pushing core config data to API."""
|
|
supervisor_client.homeassistant.set_options.side_effect = SupervisorError("boom")
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
assert "Failed to update Home Assistant options in Supervisor: boom" in caplog.text
|
|
|
|
|
|
async def test_setup_api_push_api_data_server_host(
|
|
hass: HomeAssistant, supervisor_client: AsyncMock
|
|
) -> None:
|
|
"""Test setup with API push with active server host."""
|
|
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
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
supervisor_client.homeassistant.set_options.assert_called_once_with(
|
|
HomeAssistantOptions(ssl=False, port=9999, refresh_token=ANY, watchdog=False)
|
|
)
|
|
|
|
|
|
async def test_setup_api_push_api_data_default(
|
|
hass: HomeAssistant, hass_storage: dict[str, Any], supervisor_client: AsyncMock
|
|
) -> None:
|
|
"""Test setup with API push default data."""
|
|
with (
|
|
patch.dict(os.environ, MOCK_ENVIRON),
|
|
patch("homeassistant.components.hassio.config.STORE_DELAY_SAVE", 0),
|
|
):
|
|
result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
supervisor_client.homeassistant.set_options.assert_called_once_with(
|
|
HomeAssistantOptions(ssl=False, port=8123, refresh_token=ANY)
|
|
)
|
|
refresh_token = (
|
|
supervisor_client.homeassistant.set_options.mock_calls[0].args[0].refresh_token
|
|
)
|
|
hassio_user = await hass.auth.async_get_user(
|
|
hass_storage[STORAGE_KEY]["data"]["hassio_user"]
|
|
)
|
|
assert hassio_user is not None
|
|
assert hassio_user.system_generated
|
|
assert len(hassio_user.groups) == 1
|
|
assert hassio_user.groups[0].id == GROUP_ID_ADMIN
|
|
assert hassio_user.name == "Supervisor"
|
|
for token in hassio_user.refresh_tokens.values():
|
|
if token.token == refresh_token:
|
|
break
|
|
else:
|
|
pytest.fail("refresh token not found")
|
|
|
|
|
|
async def test_setup_adds_admin_group_to_user(
|
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
|
) -> None:
|
|
"""Test setup with API push default data."""
|
|
# Create user without admin
|
|
user = await hass.auth.async_create_system_user("Hass.io")
|
|
assert not user.is_admin
|
|
await hass.auth.async_create_refresh_token(user)
|
|
|
|
hass_storage[STORAGE_KEY] = {
|
|
"data": {"hassio_user": user.id},
|
|
"key": STORAGE_KEY,
|
|
"version": 1,
|
|
}
|
|
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}})
|
|
assert result
|
|
|
|
assert user.is_admin
|
|
|
|
|
|
async def test_setup_migrate_user_name(
|
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
|
) -> None:
|
|
"""Test setup with migrating the user name."""
|
|
# Create user with old name
|
|
user = await hass.auth.async_create_system_user("Hass.io")
|
|
await hass.auth.async_create_refresh_token(user)
|
|
|
|
hass_storage[STORAGE_KEY] = {
|
|
"data": {"hassio_user": user.id},
|
|
"key": STORAGE_KEY,
|
|
"version": 1,
|
|
}
|
|
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}})
|
|
assert result
|
|
|
|
assert user.name == "Supervisor"
|
|
|
|
|
|
async def test_setup_api_existing_hassio_user(
|
|
hass: HomeAssistant, hass_storage: dict[str, Any], supervisor_client: AsyncMock
|
|
) -> None:
|
|
"""Test setup with API push default data."""
|
|
user = await hass.auth.async_create_system_user("Hass.io test")
|
|
token = await hass.auth.async_create_refresh_token(user)
|
|
hass_storage[STORAGE_KEY] = {"version": 1, "data": {"hassio_user": user.id}}
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
supervisor_client.homeassistant.set_options.assert_called_once_with(
|
|
HomeAssistantOptions(ssl=False, port=8123, refresh_token=token.token)
|
|
)
|
|
|
|
|
|
async def test_setup_core_push_config(
|
|
hass: HomeAssistant, supervisor_client: AsyncMock
|
|
) -> None:
|
|
"""Test setup with API push default data."""
|
|
hass.config.time_zone = "testzone"
|
|
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {"hassio": {}})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
supervisor_client.supervisor.set_options.assert_called_once_with(
|
|
SupervisorOptions(timezone="testzone")
|
|
)
|
|
|
|
with patch("homeassistant.util.dt.set_default_time_zone"):
|
|
await hass.config.async_update(time_zone="America/New_York", country="US")
|
|
await hass.async_block_till_done()
|
|
supervisor_client.supervisor.set_options.assert_called_with(
|
|
SupervisorOptions(timezone="America/New_York", country="US")
|
|
)
|
|
|
|
|
|
async def test_setup_core_push_config_error(
|
|
hass: HomeAssistant, supervisor_client: AsyncMock, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""Test setup with error while pushing supervisor config data to API."""
|
|
hass.config.time_zone = "testzone"
|
|
supervisor_client.supervisor.set_options.side_effect = SupervisorError("boom")
|
|
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {"hassio": {}})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
assert "Failed to update Supervisor options: boom" in caplog.text
|
|
|
|
|
|
async def test_setup_hassio_no_additional_data(
|
|
hass: HomeAssistant, supervisor_client: AsyncMock
|
|
) -> None:
|
|
"""Test setup with API push default data."""
|
|
with (
|
|
patch.dict(os.environ, MOCK_ENVIRON),
|
|
patch.dict(os.environ, {"SUPERVISOR_TOKEN": "123456"}),
|
|
):
|
|
result = await async_setup_component(hass, "hassio", {"hassio": {}})
|
|
await hass.async_block_till_done()
|
|
|
|
assert result
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
|
|
|
|
async def test_fail_setup_without_environ_var(hass: HomeAssistant) -> None:
|
|
"""Fail setup if no environ variable set."""
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
result = await async_setup_component(hass, "hassio", {})
|
|
assert not result
|
|
|
|
|
|
async def test_warn_when_cannot_connect(
|
|
hass: HomeAssistant,
|
|
caplog: pytest.LogCaptureFixture,
|
|
supervisor_is_connected: AsyncMock,
|
|
) -> None:
|
|
"""Fail warn when we cannot connect."""
|
|
supervisor_is_connected.side_effect = SupervisorError
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
result = await async_setup_component(hass, "hassio", {})
|
|
assert result
|
|
|
|
assert is_hassio(hass)
|
|
assert "Not connected with the supervisor / system too busy!" in caplog.text
|
|
|
|
|
|
@pytest.mark.usefixtures("hassio_env")
|
|
async def test_service_register(hass: HomeAssistant) -> None:
|
|
"""Check if service will be setup."""
|
|
assert await async_setup_component(hass, "hassio", {})
|
|
# New app services
|
|
assert hass.services.has_service("hassio", "app_start")
|
|
assert hass.services.has_service("hassio", "app_stop")
|
|
assert hass.services.has_service("hassio", "app_restart")
|
|
assert hass.services.has_service("hassio", "app_stdin")
|
|
# Legacy addon services (deprecated)
|
|
assert hass.services.has_service("hassio", "addon_start")
|
|
assert hass.services.has_service("hassio", "addon_stop")
|
|
assert hass.services.has_service("hassio", "addon_restart")
|
|
assert hass.services.has_service("hassio", "addon_stdin")
|
|
# Other services
|
|
assert hass.services.has_service("hassio", "host_shutdown")
|
|
assert hass.services.has_service("hassio", "host_reboot")
|
|
assert hass.services.has_service("hassio", "host_reboot")
|
|
assert hass.services.has_service("hassio", "backup_full")
|
|
assert hass.services.has_service("hassio", "backup_partial")
|
|
assert hass.services.has_service("hassio", "restore_full")
|
|
assert hass.services.has_service("hassio", "restore_partial")
|
|
assert hass.services.has_service("hassio", "mount_reload")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"app_or_addon",
|
|
["app", "addon"],
|
|
)
|
|
@pytest.mark.freeze_time("2021-11-13 11:48:00")
|
|
async def test_service_calls(
|
|
hass: HomeAssistant,
|
|
supervisor_client: AsyncMock,
|
|
supervisor_is_connected: AsyncMock,
|
|
app_or_addon: str,
|
|
) -> None:
|
|
"""Call service and check the API calls behind that."""
|
|
supervisor_is_connected.side_effect = SupervisorError
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
assert await async_setup_component(hass, "hassio", {})
|
|
await hass.async_block_till_done()
|
|
|
|
supervisor_client.reset_mock()
|
|
|
|
await hass.services.async_call(
|
|
"hassio", f"{app_or_addon}_start", {app_or_addon: "test"}
|
|
)
|
|
await hass.services.async_call(
|
|
"hassio", f"{app_or_addon}_stop", {app_or_addon: "test"}
|
|
)
|
|
await hass.services.async_call(
|
|
"hassio", f"{app_or_addon}_restart", {app_or_addon: "test"}
|
|
)
|
|
await hass.services.async_call(
|
|
"hassio", f"{app_or_addon}_stdin", {app_or_addon: "test", "input": "test"}
|
|
)
|
|
await hass.services.async_call(
|
|
"hassio",
|
|
f"{app_or_addon}_stdin",
|
|
{app_or_addon: "test", "input": {"hello": "world"}},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
supervisor_client.addons.start_addon.assert_called_once_with("test")
|
|
supervisor_client.addons.stop_addon.assert_called_once_with("test")
|
|
supervisor_client.addons.restart_addon.assert_called_once_with("test")
|
|
assert (
|
|
call("test", b'"test"') in supervisor_client.addons.write_addon_stdin.mock_calls
|
|
)
|
|
assert (
|
|
call("test", b'{"hello": "world"}')
|
|
in supervisor_client.addons.write_addon_stdin.mock_calls
|
|
)
|
|
|
|
await hass.services.async_call("hassio", "host_shutdown", {})
|
|
await hass.services.async_call("hassio", "host_reboot", {})
|
|
await hass.async_block_till_done()
|
|
|
|
supervisor_client.host.shutdown.assert_called_once_with()
|
|
supervisor_client.host.reboot.assert_called_once_with()
|
|
|
|
supervisor_client.backups.full_backup.return_value = NewBackup(
|
|
job_id=uuid4(), slug="full"
|
|
)
|
|
supervisor_client.backups.partial_backup.return_value = NewBackup(
|
|
job_id=uuid4(), slug="partial"
|
|
)
|
|
|
|
full_backup = await hass.services.async_call(
|
|
"hassio", "backup_full", {}, blocking=True, return_response=True
|
|
)
|
|
supervisor_client.backups.full_backup.assert_called_once_with(
|
|
FullBackupOptions(name="2021-11-13 03:48:00")
|
|
)
|
|
assert full_backup == {"backup": "full"}
|
|
|
|
partial_backup = await hass.services.async_call(
|
|
"hassio",
|
|
"backup_partial",
|
|
{
|
|
"homeassistant": True,
|
|
f"{app_or_addon}s": ["test"],
|
|
"folders": ["ssl"],
|
|
"password": "123456",
|
|
},
|
|
blocking=True,
|
|
return_response=True,
|
|
)
|
|
supervisor_client.backups.partial_backup.assert_called_once_with(
|
|
PartialBackupOptions(
|
|
name="2021-11-13 03:48:00",
|
|
homeassistant=True,
|
|
addons={"test"},
|
|
folders={"ssl"},
|
|
password="123456",
|
|
)
|
|
)
|
|
assert partial_backup == {"backup": "partial"}
|
|
|
|
await hass.services.async_call("hassio", "restore_full", {"slug": "test"})
|
|
await hass.services.async_call(
|
|
"hassio",
|
|
"restore_partial",
|
|
{
|
|
"slug": "test",
|
|
"homeassistant": False,
|
|
f"{app_or_addon}s": ["test"],
|
|
"folders": ["ssl"],
|
|
"password": "123456",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
supervisor_client.backups.full_restore.assert_called_once_with("test", None)
|
|
supervisor_client.backups.partial_restore.assert_called_once_with(
|
|
"test",
|
|
PartialRestoreOptions(
|
|
homeassistant=False, addons={"test"}, folders={"ssl"}, password="123456"
|
|
),
|
|
)
|
|
|
|
await hass.services.async_call(
|
|
"hassio",
|
|
"backup_full",
|
|
{
|
|
"name": "backup_name",
|
|
"location": "backup_share",
|
|
"homeassistant_exclude_database": True,
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
supervisor_client.backups.full_backup.assert_called_with(
|
|
FullBackupOptions(
|
|
name="backup_name",
|
|
location="backup_share",
|
|
homeassistant_exclude_database=True,
|
|
)
|
|
)
|
|
|
|
await hass.services.async_call(
|
|
"hassio",
|
|
"backup_full",
|
|
{
|
|
"location": "/backup",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
supervisor_client.backups.full_backup.assert_called_with(
|
|
FullBackupOptions(name="2021-11-13 03:48:00", location=None)
|
|
)
|
|
|
|
# check backup with different timezone
|
|
await hass.config.async_update(time_zone="Europe/London")
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.services.async_call(
|
|
"hassio",
|
|
"backup_full",
|
|
{
|
|
"location": "/backup",
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
supervisor_client.backups.full_backup.assert_called_with(
|
|
FullBackupOptions(name="2021-11-13 11:48:00", location=None)
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"app_or_addon",
|
|
["app", "addon"],
|
|
)
|
|
async def test_invalid_service_calls(
|
|
hass: HomeAssistant, supervisor_is_connected: AsyncMock, app_or_addon: str
|
|
) -> None:
|
|
"""Call service with invalid input and check that it raises."""
|
|
supervisor_is_connected.side_effect = SupervisorError
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
assert await async_setup_component(hass, "hassio", {})
|
|
await hass.async_block_till_done()
|
|
|
|
with pytest.raises(Invalid):
|
|
await hass.services.async_call(
|
|
"hassio", f"{app_or_addon}_start", {app_or_addon: "does_not_exist"}
|
|
)
|
|
with pytest.raises(Invalid):
|
|
await hass.services.async_call(
|
|
"hassio",
|
|
f"{app_or_addon}_stdin",
|
|
{app_or_addon: "does_not_exist", "input": "test"},
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("service", "service_data"),
|
|
[
|
|
(
|
|
"backup_partial",
|
|
{"apps": ["test"], "addons": ["test"]},
|
|
),
|
|
(
|
|
"restore_partial",
|
|
{"apps": ["test"], "addons": ["test"], "slug": "test"},
|
|
),
|
|
],
|
|
)
|
|
@pytest.mark.usefixtures("addon_installed")
|
|
async def test_service_calls_apps_addons_exclusive(
|
|
hass: HomeAssistant,
|
|
supervisor_is_connected: AsyncMock,
|
|
service: str,
|
|
service_data: dict[str, Any],
|
|
) -> None:
|
|
"""Test that apps and addons parameters are mutually exclusive."""
|
|
supervisor_is_connected.side_effect = SupervisorError
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
assert await async_setup_component(hass, "hassio", {})
|
|
await hass.async_block_till_done()
|
|
|
|
with pytest.raises(
|
|
Invalid, match="two or more values in the same group of exclusion"
|
|
):
|
|
await hass.services.async_call("hassio", service, service_data)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"app_or_addon",
|
|
["app", "addon"],
|
|
)
|
|
async def test_addon_service_call_with_complex_slug(
|
|
hass: HomeAssistant,
|
|
supervisor_is_connected: AsyncMock,
|
|
app_or_addon: str,
|
|
addons_list: AsyncMock,
|
|
) -> None:
|
|
"""Addon slugs can have ., - and _, confirm that passes validation."""
|
|
addons_list.return_value = [
|
|
InstalledAddon(
|
|
detached=False,
|
|
advanced=False,
|
|
available=True,
|
|
build=False,
|
|
description="",
|
|
homeassistant=None,
|
|
icon=False,
|
|
logo=False,
|
|
name="test.a_1-2",
|
|
repository="core",
|
|
slug="test.a_1-2",
|
|
stage=AddonStage.STABLE,
|
|
update_available=False,
|
|
url="https://github.com",
|
|
version_latest="1.0.0",
|
|
version="1.0.0",
|
|
state=AddonState.STOPPED,
|
|
)
|
|
]
|
|
supervisor_is_connected.side_effect = SupervisorError
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
assert await async_setup_component(hass, "hassio", {})
|
|
await hass.async_block_till_done()
|
|
|
|
await hass.services.async_call(
|
|
"hassio", f"{app_or_addon}_start", {app_or_addon: "test.a_1-2"}
|
|
)
|
|
|
|
|
|
@pytest.mark.usefixtures("hassio_env")
|
|
async def test_service_calls_core(
|
|
hass: HomeAssistant, supervisor_client: AsyncMock
|
|
) -> None:
|
|
"""Call core service and check the API calls behind that."""
|
|
assert await async_setup_component(hass, "homeassistant", {})
|
|
assert await async_setup_component(hass, "hassio", {})
|
|
|
|
await hass.services.async_call("homeassistant", "stop")
|
|
await hass.async_block_till_done()
|
|
|
|
supervisor_client.homeassistant.stop.assert_called_once_with()
|
|
assert len(supervisor_client.mock_calls) == 20
|
|
|
|
await hass.services.async_call("homeassistant", "check_config")
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(supervisor_client.mock_calls) == 20
|
|
|
|
with patch(
|
|
"homeassistant.config.async_check_ha_config_file", return_value=None
|
|
) as mock_check_config:
|
|
await hass.services.async_call("homeassistant", "restart")
|
|
await hass.async_block_till_done()
|
|
assert mock_check_config.called
|
|
|
|
supervisor_client.homeassistant.restart.assert_called_once_with()
|
|
assert len(supervisor_client.mock_calls) == 21
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"app_or_addon",
|
|
["apps", "addons"],
|
|
)
|
|
@pytest.mark.usefixtures("hassio_env", "supervisor_client")
|
|
async def test_invalid_service_calls_app_duplicates(
|
|
hass: HomeAssistant, app_or_addon: str
|
|
) -> None:
|
|
"""Test invalid backup/restore service calls due to duplicates in apps list."""
|
|
assert await async_setup_component(hass, "hassio", {})
|
|
|
|
with pytest.raises(Invalid, match="contains duplicate items"):
|
|
await hass.services.async_call(
|
|
"hassio", "backup_partial", {app_or_addon: ["test", "test"]}
|
|
)
|
|
|
|
with pytest.raises(Invalid, match="contains duplicate items"):
|
|
await hass.services.async_call(
|
|
"hassio", "restore_partial", {app_or_addon: ["test", "test"]}
|
|
)
|
|
|
|
|
|
@pytest.mark.usefixtures("hassio_env", "supervisor_client")
|
|
async def test_invalid_service_calls_folder_duplicates(hass: HomeAssistant) -> None:
|
|
"""Test invalid backup/restore service calls due to duplicates in folder list."""
|
|
assert await async_setup_component(hass, "hassio", {})
|
|
|
|
with pytest.raises(Invalid, match="contains duplicate items"):
|
|
await hass.services.async_call(
|
|
"hassio", "backup_partial", {"folders": ["ssl", "ssl"]}
|
|
)
|
|
|
|
with pytest.raises(Invalid, match="contains duplicate items"):
|
|
await hass.services.async_call(
|
|
"hassio", "restore_partial", {"folders": ["ssl", "ssl"]}
|
|
)
|
|
|
|
|
|
@pytest.mark.usefixtures("addon_installed")
|
|
async def test_entry_load_and_unload(hass: HomeAssistant) -> None:
|
|
"""Test loading and unloading config entry."""
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert SENSOR_DOMAIN in hass.config.components
|
|
assert BINARY_SENSOR_DOMAIN in hass.config.components
|
|
assert ADDONS_COORDINATOR in hass.data
|
|
|
|
assert await hass.config_entries.async_unload(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert ADDONS_COORDINATOR not in hass.data
|
|
|
|
|
|
async def test_migration_off_hassio(hass: HomeAssistant) -> None:
|
|
"""Test that when a user moves instance off Hass.io, config entry gets cleaned up."""
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
assert hass.config_entries.async_entries(DOMAIN) == []
|
|
|
|
|
|
@pytest.mark.usefixtures("addon_installed", "supervisor_info")
|
|
async def test_device_registry_calls(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
addons_list: AsyncMock,
|
|
os_info: AsyncMock,
|
|
) -> None:
|
|
"""Test device registry entries for hassio."""
|
|
addons_list.return_value[0] = replace(
|
|
addons_list.return_value[0],
|
|
version="1.0.0",
|
|
version_latest="1.0.0",
|
|
update_available=False,
|
|
)
|
|
addons_list.return_value[1] = replace(
|
|
addons_list.return_value[1],
|
|
version="1.0.0",
|
|
version_latest="1.0.0",
|
|
state=AddonState.STARTED,
|
|
)
|
|
os_info.return_value = replace(
|
|
os_info.return_value,
|
|
board="odroid-n2",
|
|
boot="A",
|
|
version="5.12",
|
|
version_latest="5.12",
|
|
)
|
|
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
assert len(device_registry.devices) == 6
|
|
|
|
addons_list.return_value.pop(0)
|
|
|
|
# Test that when addon is removed, next update will remove the add-on and subsequent updates won't
|
|
async_fire_time_changed(hass, dt_util.now() + timedelta(hours=1))
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
assert len(device_registry.devices) == 5
|
|
|
|
async_fire_time_changed(hass, dt_util.now() + timedelta(hours=2))
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
assert len(device_registry.devices) == 5
|
|
|
|
addons_list.return_value.append(
|
|
InstalledAddon(
|
|
detached=False,
|
|
advanced=False,
|
|
available=True,
|
|
build=False,
|
|
description="",
|
|
homeassistant=None,
|
|
icon=False,
|
|
logo=False,
|
|
name="test3",
|
|
repository="core",
|
|
slug="test3",
|
|
stage=AddonStage.STABLE,
|
|
update_available=False,
|
|
url="https://github.com",
|
|
version_latest="1.0.0",
|
|
version="1.0.0",
|
|
state=AddonState.STOPPED,
|
|
)
|
|
)
|
|
|
|
# Test that when addon is added, next update will reload the entry so we register
|
|
# a new device
|
|
async_fire_time_changed(hass, dt_util.now() + timedelta(hours=3))
|
|
await hass.async_block_till_done()
|
|
assert len(device_registry.devices) == 5
|
|
|
|
|
|
@pytest.mark.usefixtures("addon_installed")
|
|
async def test_coordinator_updates(
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, supervisor_client: AsyncMock
|
|
) -> None:
|
|
"""Test coordinator updates."""
|
|
await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
# Initial refresh, no update refresh call
|
|
supervisor_client.refresh_updates.assert_not_called()
|
|
|
|
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
# Scheduled refresh, no update refresh call
|
|
supervisor_client.refresh_updates.assert_not_called()
|
|
|
|
await hass.services.async_call(
|
|
HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
{
|
|
"entity_id": [
|
|
"update.home_assistant_core_update",
|
|
"update.home_assistant_supervisor_update",
|
|
]
|
|
},
|
|
blocking=True,
|
|
)
|
|
|
|
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
|
|
supervisor_client.refresh_updates.assert_not_called()
|
|
async_fire_time_changed(
|
|
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
|
|
)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
supervisor_client.refresh_updates.assert_called_once()
|
|
|
|
supervisor_client.refresh_updates.reset_mock()
|
|
supervisor_client.refresh_updates.side_effect = SupervisorError("Unknown")
|
|
await hass.services.async_call(
|
|
HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
{
|
|
"entity_id": [
|
|
"update.home_assistant_core_update",
|
|
"update.home_assistant_supervisor_update",
|
|
]
|
|
},
|
|
blocking=True,
|
|
)
|
|
# 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()
|
|
supervisor_client.refresh_updates.assert_called_once()
|
|
assert "Error on Supervisor API: Unknown" in caplog.text
|
|
|
|
|
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "addon_installed")
|
|
async def test_coordinator_updates_stats_entities_enabled(
|
|
hass: HomeAssistant,
|
|
caplog: pytest.LogCaptureFixture,
|
|
supervisor_client: AsyncMock,
|
|
) -> None:
|
|
"""Test coordinator updates with stats entities enabled."""
|
|
await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
# Initial refresh without stats
|
|
supervisor_client.refresh_updates.assert_not_called()
|
|
|
|
# Refresh with stats once we know which ones are needed
|
|
async_fire_time_changed(
|
|
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
supervisor_client.refresh_updates.assert_called_once()
|
|
|
|
supervisor_client.refresh_updates.reset_mock()
|
|
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
|
|
await hass.async_block_till_done()
|
|
supervisor_client.refresh_updates.assert_not_called()
|
|
|
|
await hass.services.async_call(
|
|
HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
{
|
|
"entity_id": [
|
|
"update.home_assistant_core_update",
|
|
"update.home_assistant_supervisor_update",
|
|
]
|
|
},
|
|
blocking=True,
|
|
)
|
|
supervisor_client.refresh_updates.assert_not_called()
|
|
|
|
# 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()
|
|
|
|
supervisor_client.refresh_updates.reset_mock()
|
|
supervisor_client.refresh_updates.side_effect = SupervisorError("Unknown")
|
|
await hass.services.async_call(
|
|
HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
{
|
|
"entity_id": [
|
|
"update.home_assistant_core_update",
|
|
"update.home_assistant_supervisor_update",
|
|
]
|
|
},
|
|
blocking=True,
|
|
)
|
|
# 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()
|
|
supervisor_client.refresh_updates.assert_called_once()
|
|
assert "Error on Supervisor API: Unknown" in caplog.text
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("board", "integration"),
|
|
[
|
|
("green", "homeassistant_green"),
|
|
("odroid-c2", "hardkernel"),
|
|
("odroid-c4", "hardkernel"),
|
|
("odroid-n2", "hardkernel"),
|
|
("odroid-xu4", "hardkernel"),
|
|
("rpi2", "raspberry_pi"),
|
|
("rpi3", "raspberry_pi"),
|
|
("rpi3-64", "raspberry_pi"),
|
|
("rpi4", "raspberry_pi"),
|
|
("rpi4-64", "raspberry_pi"),
|
|
("yellow", "homeassistant_yellow"),
|
|
],
|
|
)
|
|
async def test_setup_hardware_integration(
|
|
hass: HomeAssistant,
|
|
supervisor_client: AsyncMock,
|
|
os_info: AsyncMock,
|
|
board: str,
|
|
integration: str,
|
|
) -> None:
|
|
"""Test setup initiates hardware integration."""
|
|
os_info.return_value = replace(os_info.return_value, board=board)
|
|
|
|
with (
|
|
patch.dict(os.environ, MOCK_ENVIRON),
|
|
patch(
|
|
f"homeassistant.components.{integration}.async_setup_entry",
|
|
return_value=True,
|
|
) as mock_setup_entry,
|
|
patch(
|
|
"homeassistant.components.homeassistant_yellow.config_flow.probe_silabs_firmware_info",
|
|
return_value=None,
|
|
),
|
|
):
|
|
result = await async_setup_component(hass, "hassio", {"hassio": {}})
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
assert result
|
|
assert len(supervisor_client.mock_calls) == 23
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
def test_hostname_from_addon_slug() -> None:
|
|
"""Test hostname_from_addon_slug."""
|
|
assert hostname_from_addon_slug("mqtt") == "mqtt"
|
|
assert (
|
|
hostname_from_addon_slug("core_silabs_multiprotocol")
|
|
== "core-silabs-multiprotocol"
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("board", "issue_id"),
|
|
[
|
|
("rpi3", "deprecated_os_aarch64"),
|
|
("rpi4", "deprecated_os_aarch64"),
|
|
("tinker", "deprecated_os_armv7"),
|
|
("odroid-xu4", "deprecated_os_armv7"),
|
|
("rpi2", "deprecated_os_armv7"),
|
|
],
|
|
)
|
|
async def test_deprecated_installation_issue_os_armv7(
|
|
hass: HomeAssistant,
|
|
issue_registry: ir.IssueRegistry,
|
|
freezer: FrozenDateTimeFactory,
|
|
board: str,
|
|
issue_id: str,
|
|
) -> None:
|
|
"""Test deprecated installation issue."""
|
|
with (
|
|
patch.dict(os.environ, MOCK_ENVIRON),
|
|
patch(
|
|
"homeassistant.components.hassio._is_32_bit",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_os_info", return_value={"board": board}
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_info",
|
|
return_value={"hassos": True, "arch": "armv7"},
|
|
),
|
|
patch("homeassistant.components.hardware.async_setup", return_value=True),
|
|
):
|
|
assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
freezer.tick(REQUEST_REFRESH_DELAY)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
await hass.services.async_call(
|
|
HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
{
|
|
"entity_id": [
|
|
"update.home_assistant_core_update",
|
|
"update.home_assistant_supervisor_update",
|
|
]
|
|
},
|
|
blocking=True,
|
|
)
|
|
freezer.tick(HASSIO_UPDATE_INTERVAL)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(issue_registry.issues) == 1
|
|
issue = issue_registry.async_get_issue("homeassistant", issue_id)
|
|
assert issue.domain == "homeassistant"
|
|
assert issue.severity == ir.IssueSeverity.WARNING
|
|
assert issue.translation_placeholders == {
|
|
"installation_guide": "https://www.home-assistant.io/installation/",
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"arch",
|
|
[
|
|
"i386",
|
|
"armhf",
|
|
"armv7",
|
|
],
|
|
)
|
|
async def test_deprecated_installation_issue_32bit_os(
|
|
hass: HomeAssistant,
|
|
issue_registry: ir.IssueRegistry,
|
|
freezer: FrozenDateTimeFactory,
|
|
arch: str,
|
|
) -> None:
|
|
"""Test deprecated architecture issue."""
|
|
with (
|
|
patch.dict(os.environ, MOCK_ENVIRON),
|
|
patch(
|
|
"homeassistant.components.hassio._is_32_bit",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_os_info",
|
|
return_value={"board": "rpi3-64"},
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_info",
|
|
return_value={"hassos": True, "arch": arch},
|
|
),
|
|
patch("homeassistant.components.hardware.async_setup", return_value=True),
|
|
):
|
|
assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
freezer.tick(REQUEST_REFRESH_DELAY)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
await hass.services.async_call(
|
|
HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
{
|
|
"entity_id": [
|
|
"update.home_assistant_core_update",
|
|
"update.home_assistant_supervisor_update",
|
|
]
|
|
},
|
|
blocking=True,
|
|
)
|
|
freezer.tick(HASSIO_UPDATE_INTERVAL)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(issue_registry.issues) == 1
|
|
issue = issue_registry.async_get_issue("homeassistant", "deprecated_architecture")
|
|
assert issue.domain == "homeassistant"
|
|
assert issue.severity == ir.IssueSeverity.WARNING
|
|
assert issue.translation_placeholders == {"installation_type": "OS", "arch": arch}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"arch",
|
|
[
|
|
"i386",
|
|
"armhf",
|
|
"armv7",
|
|
],
|
|
)
|
|
async def test_deprecated_installation_issue_32bit_supervised(
|
|
hass: HomeAssistant,
|
|
issue_registry: ir.IssueRegistry,
|
|
freezer: FrozenDateTimeFactory,
|
|
arch: str,
|
|
) -> None:
|
|
"""Test deprecated architecture issue."""
|
|
with (
|
|
patch.dict(os.environ, MOCK_ENVIRON),
|
|
patch(
|
|
"homeassistant.components.hassio._is_32_bit",
|
|
return_value=True,
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_os_info",
|
|
return_value={"board": "rpi3-64"},
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_info",
|
|
return_value={"hassos": None, "arch": arch},
|
|
),
|
|
patch("homeassistant.components.hardware.async_setup", return_value=True),
|
|
):
|
|
assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
freezer.tick(REQUEST_REFRESH_DELAY)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
await hass.services.async_call(
|
|
HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
{
|
|
"entity_id": [
|
|
"update.home_assistant_core_update",
|
|
"update.home_assistant_supervisor_update",
|
|
]
|
|
},
|
|
blocking=True,
|
|
)
|
|
freezer.tick(HASSIO_UPDATE_INTERVAL)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(issue_registry.issues) == 1
|
|
issue = issue_registry.async_get_issue(
|
|
"homeassistant", "deprecated_method_architecture"
|
|
)
|
|
assert issue.domain == "homeassistant"
|
|
assert issue.severity == ir.IssueSeverity.WARNING
|
|
assert issue.translation_placeholders == {
|
|
"installation_type": "Supervised",
|
|
"arch": arch,
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"arch",
|
|
[
|
|
"amd64",
|
|
"aarch64",
|
|
],
|
|
)
|
|
async def test_deprecated_installation_issue_64bit_supervised(
|
|
hass: HomeAssistant,
|
|
issue_registry: ir.IssueRegistry,
|
|
freezer: FrozenDateTimeFactory,
|
|
arch: str,
|
|
) -> None:
|
|
"""Test deprecated architecture issue."""
|
|
with (
|
|
patch.dict(os.environ, MOCK_ENVIRON),
|
|
patch(
|
|
"homeassistant.components.hassio._is_32_bit",
|
|
return_value=False,
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_os_info",
|
|
return_value={"board": "generic-x86-64"},
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_info",
|
|
return_value={"hassos": None, "arch": arch},
|
|
),
|
|
patch("homeassistant.components.hardware.async_setup", return_value=True),
|
|
):
|
|
assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
freezer.tick(REQUEST_REFRESH_DELAY)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
await hass.services.async_call(
|
|
HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
{
|
|
"entity_id": [
|
|
"update.home_assistant_core_update",
|
|
"update.home_assistant_supervisor_update",
|
|
]
|
|
},
|
|
blocking=True,
|
|
)
|
|
freezer.tick(HASSIO_UPDATE_INTERVAL)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(issue_registry.issues) == 1
|
|
issue = issue_registry.async_get_issue("homeassistant", "deprecated_method")
|
|
assert issue.domain == "homeassistant"
|
|
assert issue.severity == ir.IssueSeverity.WARNING
|
|
assert issue.translation_placeholders == {
|
|
"installation_type": "Supervised",
|
|
"arch": arch,
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("board", "issue_id"),
|
|
[
|
|
("rpi5", "deprecated_os_aarch64"),
|
|
],
|
|
)
|
|
async def test_deprecated_installation_issue_supported_board(
|
|
hass: HomeAssistant,
|
|
issue_registry: ir.IssueRegistry,
|
|
freezer: FrozenDateTimeFactory,
|
|
board: str,
|
|
issue_id: str,
|
|
) -> None:
|
|
"""Test no deprecated installation issue for a supported board."""
|
|
with (
|
|
patch.dict(os.environ, MOCK_ENVIRON),
|
|
patch(
|
|
"homeassistant.components.hassio._is_32_bit",
|
|
return_value=False,
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_os_info", return_value={"board": board}
|
|
),
|
|
patch(
|
|
"homeassistant.components.hassio.get_info",
|
|
return_value={"hassos": True, "arch": "aarch64"},
|
|
),
|
|
):
|
|
assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
freezer.tick(REQUEST_REFRESH_DELAY)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
await hass.services.async_call(
|
|
HOMEASSISTANT_DOMAIN,
|
|
SERVICE_UPDATE_ENTITY,
|
|
{
|
|
"entity_id": [
|
|
"update.home_assistant_core_update",
|
|
"update.home_assistant_supervisor_update",
|
|
]
|
|
},
|
|
blocking=True,
|
|
)
|
|
freezer.tick(HASSIO_UPDATE_INTERVAL)
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(issue_registry.issues) == 0
|
|
|
|
|
|
async def mount_reload_test_setup(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
supervisor_client: AsyncMock,
|
|
) -> dr.DeviceEntry:
|
|
"""Set up mount reload test and return the device entry."""
|
|
supervisor_client.mounts.info = AsyncMock(
|
|
return_value=MountsInfo(
|
|
default_backup_mount=None,
|
|
mounts=[
|
|
CIFSMountResponse(
|
|
share="files",
|
|
server="1.2.3.4",
|
|
name="NAS",
|
|
type=MountType.CIFS,
|
|
usage=MountUsage.SHARE,
|
|
read_only=False,
|
|
state=MountState.ACTIVE,
|
|
user_path=PurePath("/share/nas"),
|
|
)
|
|
],
|
|
)
|
|
)
|
|
|
|
with patch.dict(os.environ, MOCK_ENVIRON):
|
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
device = device_registry.async_get_device(identifiers={(DOMAIN, "mount_NAS")})
|
|
assert device is not None
|
|
return device
|
|
|
|
|
|
async def test_mount_reload_action(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
supervisor_client: AsyncMock,
|
|
) -> None:
|
|
"""Test reload_mount service call."""
|
|
device = await mount_reload_test_setup(hass, device_registry, supervisor_client)
|
|
await hass.services.async_call(
|
|
"hassio", "mount_reload", {"device_id": device.id}, blocking=True
|
|
)
|
|
supervisor_client.mounts.reload_mount.assert_awaited_once_with("NAS")
|
|
|
|
|
|
async def test_mount_reload_action_failure(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
supervisor_client: AsyncMock,
|
|
) -> None:
|
|
"""Test reload_mount service call failure."""
|
|
device = await mount_reload_test_setup(hass, device_registry, supervisor_client)
|
|
supervisor_client.mounts.reload_mount = AsyncMock(
|
|
side_effect=SupervisorError("test failure")
|
|
)
|
|
with pytest.raises(HomeAssistantError) as exc:
|
|
await hass.services.async_call(
|
|
"hassio", "mount_reload", {"device_id": device.id}, blocking=True
|
|
)
|
|
assert str(exc.value) == "Failed to reload mount NAS: test failure"
|
|
|
|
|
|
async def test_mount_reload_unknown_device_id(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
supervisor_client: AsyncMock,
|
|
) -> None:
|
|
"""Test reload_mount with unknown device ID."""
|
|
await mount_reload_test_setup(hass, device_registry, supervisor_client)
|
|
with pytest.raises(ServiceValidationError) as exc:
|
|
await hass.services.async_call(
|
|
"hassio", "mount_reload", {"device_id": "1234"}, blocking=True
|
|
)
|
|
assert str(exc.value) == "Device ID not found"
|
|
|
|
|
|
async def test_mount_reload_no_name(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
supervisor_client: AsyncMock,
|
|
) -> None:
|
|
"""Test reload_mount with an unnamed device."""
|
|
device = await mount_reload_test_setup(hass, device_registry, supervisor_client)
|
|
device_registry.async_update_device(device.id, name=None)
|
|
with pytest.raises(ServiceValidationError) as exc:
|
|
await hass.services.async_call(
|
|
"hassio", "mount_reload", {"device_id": device.id}, blocking=True
|
|
)
|
|
assert str(exc.value) == "Device is not a supervisor mount point"
|
|
|
|
|
|
async def test_mount_reload_invalid_model(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
supervisor_client: AsyncMock,
|
|
) -> None:
|
|
"""Test reload_mount with an invalid model."""
|
|
device = await mount_reload_test_setup(hass, device_registry, supervisor_client)
|
|
device_registry.async_update_device(device.id, model=None)
|
|
with pytest.raises(ServiceValidationError) as exc:
|
|
await hass.services.async_call(
|
|
"hassio", "mount_reload", {"device_id": device.id}, blocking=True
|
|
)
|
|
assert str(exc.value) == "Device is not a supervisor mount point"
|
|
|
|
|
|
async def test_mount_reload_not_supervisor_device(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
supervisor_client: AsyncMock,
|
|
) -> None:
|
|
"""Test reload_mount with a device not belonging to the supervisor."""
|
|
device = await mount_reload_test_setup(hass, device_registry, supervisor_client)
|
|
config_entry = MockConfigEntry()
|
|
config_entry.add_to_hass(hass)
|
|
device2 = device_registry.async_get_or_create(
|
|
config_entry_id=config_entry.entry_id,
|
|
identifiers={("test", "test")},
|
|
name=device.name,
|
|
model=device.model,
|
|
)
|
|
with pytest.raises(ServiceValidationError) as exc:
|
|
await hass.services.async_call(
|
|
"hassio", "mount_reload", {"device_id": device2.id}, blocking=True
|
|
)
|
|
assert str(exc.value) == "Device is not a supervisor mount point"
|
|
|
|
|
|
async def test_mount_reload_selector_matches_device_name(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
supervisor_client: AsyncMock,
|
|
) -> None:
|
|
"""Test that the model name in the selector of mount reload is valid."""
|
|
device = await mount_reload_test_setup(hass, device_registry, supervisor_client)
|
|
services = load_yaml_dict(f"{hassio.__path__[0]}/services.yaml")
|
|
assert (
|
|
services["mount_reload"]["fields"]["device_id"]["selector"]["device"]["filter"][
|
|
"model"
|
|
]
|
|
== device.model
|
|
)
|