mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-11-08 02:19:35 +00:00
* Formally deprecate CodeNotary build config * Remove CodeNotary specific integrity checking The current code is specific to how CodeNotary was doing integrity checking. A future integrity checking mechanism likely will work differently (e.g. through EROFS based containers). Remove the current code to make way for a future implementation. * Drop CodeNotary integrity fixups * Drop unused tests * Fix pytest * Fix pytest * Remove CodeNotary related exceptions and handling Remove CodeNotary related exceptions and handling from the Docker interface. * Drop unnecessary comment * Remove Codenotary specific IssueType/SuggestionType * Drop Codenotary specific environment and secret reference * Remove unused constants * Introduce APIGone exception for removed APIs Introduce a new exception class APIGone to indicate that certain API features have been removed and are no longer available. Update the security integrity check endpoint to raise this new exception instead of a generic APIError, providing clearer communication to clients that the feature has been intentionally removed. * Drop content trust A cosign based signature verification will likely be named differently to avoid confusion with existing implementations. For now, remove the content trust option entirely. * Drop code sign test * Remove source_mods/content_trust evaluations * Remove content_trust reference in bootstrap.py * Fix security tests * Drop unused tests * Drop codenotary from schema Since we have "remove extra" in voluptuous, we can remove the codenotary field from the addon schema. * Remove content_trust from tests * Remove content_trust unsupported reason * Remove unnecessary comment * Remove unrelated pytest * Remove unrelated fixtures
216 lines
8.0 KiB
Python
216 lines
8.0 KiB
Python
"""Test updater files."""
|
|
|
|
import asyncio
|
|
import json
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from awesomeversion import AwesomeVersion
|
|
import pytest
|
|
|
|
from supervisor.const import ATTR_HASSOS_UNRESTRICTED, BusEvent
|
|
from supervisor.coresys import CoreSys
|
|
from supervisor.dbus.const import ConnectivityState
|
|
from supervisor.exceptions import UpdaterJobError
|
|
from supervisor.jobs import SupervisorJob
|
|
from supervisor.resolution.const import UnsupportedReason
|
|
|
|
from tests.common import MockResponse, load_binary_fixture
|
|
from tests.dbus_service_mocks.network_manager import (
|
|
NetworkManager as NetworkManagerService,
|
|
)
|
|
|
|
URL_TEST = "https://version.home-assistant.io/stable.json"
|
|
|
|
|
|
@pytest.mark.usefixtures("no_job_throttle")
|
|
async def test_fetch_versions(
|
|
coresys: CoreSys, mock_update_data: MockResponse, supervisor_internet: AsyncMock
|
|
) -> None:
|
|
"""Test download and sync version."""
|
|
|
|
coresys.security.force = True
|
|
await coresys.updater.fetch_data()
|
|
|
|
data = json.loads(await mock_update_data.text())
|
|
assert coresys.updater.version_supervisor == data["supervisor"]
|
|
assert coresys.updater.version_homeassistant == data["homeassistant"]["default"]
|
|
|
|
assert coresys.updater.version_audio == data["audio"]
|
|
assert coresys.updater.version_cli == data["cli"]
|
|
assert coresys.updater.version_dns == data["dns"]
|
|
assert coresys.updater.version_multicast == data["multicast"]
|
|
assert coresys.updater.version_observer == data["observer"]
|
|
|
|
assert coresys.updater.image_homeassistant == data["images"]["core"].format(
|
|
machine=coresys.machine
|
|
)
|
|
|
|
assert coresys.updater.image_supervisor == data["images"]["supervisor"].format(
|
|
arch=coresys.arch.supervisor
|
|
)
|
|
assert coresys.updater.image_cli == data["images"]["cli"].format(
|
|
arch=coresys.arch.supervisor
|
|
)
|
|
assert coresys.updater.image_audio == data["images"]["audio"].format(
|
|
arch=coresys.arch.supervisor
|
|
)
|
|
assert coresys.updater.image_dns == data["images"]["dns"].format(
|
|
arch=coresys.arch.supervisor
|
|
)
|
|
assert coresys.updater.image_observer == data["images"]["observer"].format(
|
|
arch=coresys.arch.supervisor
|
|
)
|
|
assert coresys.updater.image_multicast == data["images"]["multicast"].format(
|
|
arch=coresys.arch.supervisor
|
|
)
|
|
|
|
|
|
@pytest.mark.usefixtures("no_job_throttle")
|
|
@pytest.mark.parametrize(
|
|
"version, expected",
|
|
[
|
|
("3.1", "3.13"),
|
|
("4.5", "4.20"),
|
|
("5.0", "5.13"),
|
|
("6.4", "6.6"),
|
|
("4.20", "5.13"),
|
|
],
|
|
)
|
|
async def test_os_update_path(
|
|
coresys: CoreSys,
|
|
version: str,
|
|
expected: str,
|
|
mock_update_data: AsyncMock,
|
|
supervisor_internet: AsyncMock,
|
|
):
|
|
"""Test OS upgrade path across major versions."""
|
|
coresys.os._board = "rpi4" # pylint: disable=protected-access
|
|
coresys.os._version = AwesomeVersion(version) # pylint: disable=protected-access
|
|
await coresys.updater.fetch_data()
|
|
|
|
assert coresys.updater.version_hassos == AwesomeVersion(expected)
|
|
|
|
|
|
@pytest.mark.usefixtures("no_job_throttle")
|
|
async def test_delayed_fetch_for_connectivity(
|
|
coresys: CoreSys,
|
|
network_manager_service: NetworkManagerService,
|
|
websession: MagicMock,
|
|
):
|
|
"""Test initial version fetch waits for connectivity on load."""
|
|
coresys.websession.get = MagicMock()
|
|
coresys.websession.get.return_value.__aenter__.return_value.status = 200
|
|
coresys.websession.get.return_value.__aenter__.return_value.read.return_value = (
|
|
load_binary_fixture("version_stable.json")
|
|
)
|
|
coresys.websession.head = AsyncMock()
|
|
|
|
# Network connectivity change causes a series of async tasks to eventually do a version fetch
|
|
# Rather then use some kind of sleep loop, set up listener for start of fetch data job
|
|
event = asyncio.Event()
|
|
|
|
async def find_fetch_data_job_start(job: SupervisorJob):
|
|
if job.name == "updater_fetch_data":
|
|
event.set()
|
|
|
|
coresys.bus.register_event(BusEvent.SUPERVISOR_JOB_START, find_fetch_data_job_start)
|
|
|
|
# Start with no connectivity and confirm there is no version fetch on load
|
|
coresys.supervisor.connectivity = False
|
|
network_manager_service.connectivity = ConnectivityState.CONNECTIVITY_NONE.value
|
|
await coresys.host.network.load()
|
|
await coresys.host.network.check_connectivity()
|
|
|
|
await coresys.updater.load()
|
|
await coresys.updater.reload()
|
|
coresys.websession.get.assert_not_called()
|
|
|
|
# Now signal host has connectivity and wait for fetch data to complete to assert
|
|
network_manager_service.emit_properties_changed(
|
|
{"Connectivity": ConnectivityState.CONNECTIVITY_FULL}
|
|
)
|
|
await network_manager_service.ping()
|
|
async with asyncio.timeout(5):
|
|
await event.wait()
|
|
await asyncio.sleep(0)
|
|
|
|
coresys.websession.get.assert_called_once()
|
|
assert (
|
|
coresys.websession.get.call_args[0][0]
|
|
== "https://version.home-assistant.io/stable.json"
|
|
)
|
|
|
|
|
|
@pytest.mark.usefixtures("no_job_throttle")
|
|
async def test_load_calls_reload_when_os_board_without_version(
|
|
coresys: CoreSys, mock_update_data: MockResponse, supervisor_internet: AsyncMock
|
|
) -> None:
|
|
"""Test load calls reload when OS board exists but no version_hassos_unrestricted."""
|
|
# Set up OS board but no version data
|
|
coresys.os._board = "rpi4" # pylint: disable=protected-access
|
|
coresys.security.force = True
|
|
|
|
# Mock reload to verify it gets called
|
|
with patch.object(coresys.updater, "reload", new_callable=AsyncMock) as mock_reload:
|
|
await coresys.updater.load()
|
|
mock_reload.assert_called_once()
|
|
|
|
|
|
@pytest.mark.usefixtures("no_job_throttle")
|
|
async def test_load_skips_reload_when_os_board_with_version(
|
|
coresys: CoreSys, mock_update_data: MockResponse, supervisor_internet: AsyncMock
|
|
) -> None:
|
|
"""Test load skips reload when OS board exists and version_hassos_unrestricted is set."""
|
|
# Set up OS board and version data
|
|
coresys.os._board = "rpi4" # pylint: disable=protected-access
|
|
coresys.security.force = True
|
|
|
|
# Pre-populate version_hassos_unrestricted by setting it directly on the data dict
|
|
# Use the same approach as other tests that modify internal state
|
|
coresys.updater._data[ATTR_HASSOS_UNRESTRICTED] = AwesomeVersion("13.1") # pylint: disable=protected-access
|
|
|
|
# Mock reload to verify it doesn't get called
|
|
with patch.object(coresys.updater, "reload", new_callable=AsyncMock) as mock_reload:
|
|
await coresys.updater.load()
|
|
mock_reload.assert_not_called()
|
|
|
|
|
|
@pytest.mark.usefixtures("no_job_throttle")
|
|
async def test_load_skips_reload_when_no_os_board(
|
|
coresys: CoreSys, mock_update_data: MockResponse, supervisor_internet: AsyncMock
|
|
) -> None:
|
|
"""Test load skips reload when no OS board is set."""
|
|
# Ensure no OS board is set
|
|
coresys.os._board = None # pylint: disable=protected-access
|
|
|
|
# Mock reload to verify it doesn't get called
|
|
with patch.object(coresys.updater, "reload", new_callable=AsyncMock) as mock_reload:
|
|
await coresys.updater.load()
|
|
mock_reload.assert_not_called()
|
|
|
|
|
|
async def test_fetch_data_no_update_when_os_unsupported(
|
|
coresys: CoreSys, websession: MagicMock
|
|
) -> None:
|
|
"""Test that fetch_data doesn't update data when OS is unsupported."""
|
|
# Store initial versions to compare later
|
|
initial_supervisor_version = coresys.updater.version_supervisor
|
|
initial_homeassistant_version = coresys.updater.version_homeassistant
|
|
initial_hassos_version = coresys.updater.version_hassos
|
|
|
|
coresys.websession.head = AsyncMock()
|
|
|
|
# Mark OS as unsupported by adding UnsupportedReason.OS_VERSION
|
|
coresys.resolution.unsupported.append(UnsupportedReason.OS_VERSION)
|
|
|
|
# Attempt to fetch data should fail due to OS_SUPPORTED condition
|
|
with pytest.raises(
|
|
UpdaterJobError, match="blocked from execution, unsupported OS version"
|
|
):
|
|
await coresys.updater.fetch_data()
|
|
|
|
# Verify that versions were not updated
|
|
assert coresys.updater.version_supervisor == initial_supervisor_version
|
|
assert coresys.updater.version_homeassistant == initial_homeassistant_version
|
|
assert coresys.updater.version_hassos == initial_hassos_version
|