mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-07-08 14:23:51 +01:00
81e235376e
* Fix typos in comments, docstrings and log messages Correct 39 spelling mistakes across comments, docstrings and log/error message strings throughout the package (e.g. "conection" -> "connection", "Incomming" -> "Incoming", "Rasie" -> "Raise"). All changes are confined to human-readable text; no identifiers, attributes or D-Bus contracts are touched, so there is no behavior change. Found with codespell. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix typos in tests and CI workflow Correct spelling mistakes in test comments, docstrings and data, plus one in the builder workflow, so the whole tree is clean for the codespell hook added next. The assertion in test_network_manager.py is updated to match the corrected "Unknown error while processing" log message in the source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add codespell pre-commit hook Wire up codespell so spelling mistakes in comments, docstrings and strings are caught automatically. The vendored frontend panel is excluded, and "hass" and "astroid" are added to the ignore list as known false positives (the Home Assistant abbreviation and the pylint dependency package). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address review feedback Improve grammar in several of the touched comments and docstrings: use the plural "ignore conditions" for the list-returning property, add the missing auxiliary verb and fix agreement in the timezone-filter comment, fix "backups ... use" agreement, and reword "underlay" to "underlying" in the arch module docstring. Also drop the "*.json" skip from the codespell hook. It was carried over from another project but is unnecessary here (all tracked JSON is clean), and skipping it would needlessly leave translation and data JSON unchecked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Reword onboarding comment "overflight" was a literal calque of the German "überflogen"; use the idiomatic "skimmed through" instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
276 lines
10 KiB
Python
276 lines
10 KiB
Python
"""Test NetworkInterface."""
|
|
|
|
import logging
|
|
from unittest.mock import Mock, PropertyMock, patch
|
|
|
|
from dbus_fast.aio.message_bus import MessageBus
|
|
import pytest
|
|
|
|
from supervisor.dbus.const import ConnectionState
|
|
from supervisor.dbus.network import NetworkManager
|
|
from supervisor.dbus.network.interface import NetworkInterface
|
|
from supervisor.exceptions import (
|
|
DBusFatalError,
|
|
DBusParseError,
|
|
DBusServiceUnkownError,
|
|
HostNotSupportedError,
|
|
)
|
|
from supervisor.utils.dbus import DBus
|
|
|
|
from tests.const import TEST_INTERFACE_ETH_NAME, TEST_INTERFACE_WLAN_NAME
|
|
from tests.dbus_service_mocks.base import DBusServiceMock
|
|
from tests.dbus_service_mocks.network_connection_settings import SETTINGS_1_FIXTURE
|
|
from tests.dbus_service_mocks.network_manager import (
|
|
NetworkManager as NetworkManagerService,
|
|
)
|
|
|
|
|
|
@pytest.fixture(name="network_manager_service")
|
|
async def fixture_network_manager_service(
|
|
network_manager_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
|
|
) -> NetworkManagerService:
|
|
"""Mock NetworkManager dbus service."""
|
|
return network_manager_services["network_manager"]
|
|
|
|
|
|
async def test_network_manager(
|
|
network_manager_service: NetworkManagerService, dbus_session_bus: MessageBus
|
|
):
|
|
"""Test network manager update."""
|
|
network_manager = NetworkManager()
|
|
|
|
assert network_manager.connectivity_enabled is None
|
|
|
|
await network_manager.connect(dbus_session_bus)
|
|
|
|
assert TEST_INTERFACE_ETH_NAME in network_manager
|
|
assert network_manager.connectivity_enabled is True
|
|
|
|
network_manager_service.emit_properties_changed({"ConnectivityCheckEnabled": False})
|
|
await network_manager_service.ping()
|
|
assert network_manager.connectivity_enabled is False
|
|
|
|
network_manager_service.emit_properties_changed({}, ["ConnectivityCheckEnabled"])
|
|
await network_manager_service.ping()
|
|
await network_manager_service.ping()
|
|
assert network_manager.connectivity_enabled is True
|
|
|
|
|
|
async def test_network_manager_version(
|
|
network_manager_service: NetworkManagerService, network_manager: NetworkManager
|
|
):
|
|
"""Test if version validate work."""
|
|
# pylint: disable=protected-access
|
|
await network_manager._validate_version()
|
|
assert network_manager.version == "1.22.10"
|
|
|
|
network_manager_service.version = "1.13.9"
|
|
with pytest.raises(HostNotSupportedError):
|
|
await network_manager._validate_version()
|
|
assert network_manager.version == "1.13.9"
|
|
# pylint: enable=protected-access
|
|
|
|
|
|
async def test_check_connectivity(
|
|
network_manager_service: NetworkManagerService, network_manager: NetworkManager
|
|
):
|
|
"""Test connectivity check."""
|
|
network_manager_service.CheckConnectivity.calls.clear()
|
|
|
|
assert await network_manager.check_connectivity() == 4
|
|
assert network_manager_service.CheckConnectivity.calls == []
|
|
|
|
assert await network_manager.check_connectivity(force=True) == 4
|
|
assert network_manager_service.CheckConnectivity.calls == [()]
|
|
|
|
|
|
async def test_activate_connection(
|
|
network_manager_service: NetworkManagerService, network_manager: NetworkManager
|
|
):
|
|
"""Test activate connection."""
|
|
network_manager_service.ActivateConnection.calls.clear()
|
|
connection = await network_manager.activate_connection(
|
|
"/org/freedesktop/NetworkManager/Settings/1",
|
|
"/org/freedesktop/NetworkManager/Devices/1",
|
|
)
|
|
assert connection.state == ConnectionState.ACTIVATED
|
|
assert (
|
|
connection.settings.object_path == "/org/freedesktop/NetworkManager/Settings/1"
|
|
)
|
|
assert network_manager_service.ActivateConnection.calls == [
|
|
(
|
|
"/org/freedesktop/NetworkManager/Settings/1",
|
|
"/org/freedesktop/NetworkManager/Devices/1",
|
|
"/",
|
|
)
|
|
]
|
|
|
|
|
|
async def test_add_and_activate_connection(
|
|
network_manager_service: NetworkManagerService, network_manager: NetworkManager
|
|
):
|
|
"""Test add and activate connection."""
|
|
network_manager_service.AddAndActivateConnection.calls.clear()
|
|
|
|
settings, connection = await network_manager.add_and_activate_connection(
|
|
SETTINGS_1_FIXTURE, "/org/freedesktop/NetworkManager/Devices/1"
|
|
)
|
|
assert settings.connection.uuid == "0c23631e-2118-355c-bbb0-8943229cb0d6"
|
|
assert settings.ipv4.method == "auto"
|
|
assert connection.state == ConnectionState.ACTIVATED
|
|
assert (
|
|
connection.settings.object_path == "/org/freedesktop/NetworkManager/Settings/1"
|
|
)
|
|
assert network_manager_service.AddAndActivateConnection.calls == [
|
|
(SETTINGS_1_FIXTURE, "/org/freedesktop/NetworkManager/Devices/1", "/")
|
|
]
|
|
|
|
|
|
async def test_removed_devices_disconnect(
|
|
network_manager_service: NetworkManagerService, network_manager: NetworkManager
|
|
):
|
|
"""Test removed devices are disconnected."""
|
|
wlan = network_manager.get(TEST_INTERFACE_WLAN_NAME)
|
|
assert wlan.is_connected is True
|
|
|
|
network_manager_service.emit_properties_changed({"Devices": []})
|
|
await network_manager_service.ping()
|
|
|
|
assert TEST_INTERFACE_WLAN_NAME not in network_manager
|
|
assert wlan.is_connected is False
|
|
|
|
|
|
async def test_handling_bad_devices(
|
|
network_manager_service: NetworkManagerService,
|
|
network_manager: NetworkManager,
|
|
caplog: pytest.LogCaptureFixture,
|
|
capture_exception: Mock,
|
|
):
|
|
"""Test handling of bad and disappearing devices."""
|
|
caplog.clear()
|
|
caplog.set_level(logging.INFO, "supervisor.dbus.network")
|
|
|
|
with patch.object(DBus, "init_proxy", side_effect=DBusFatalError()):
|
|
await network_manager.update(
|
|
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/100"]}
|
|
)
|
|
assert f"Can't process {device}" not in caplog.text
|
|
|
|
await network_manager.update()
|
|
with patch.object(DBus, "properties", new=PropertyMock(return_value=None)):
|
|
await network_manager.update(
|
|
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/101"]}
|
|
)
|
|
assert f"Can't process {device}" not in caplog.text
|
|
|
|
# Unparsable introspections shouldn't happen, this one is logged and captured
|
|
await network_manager.update()
|
|
with patch.object(DBus, "init_proxy", side_effect=(err := DBusParseError())):
|
|
await network_manager.update(
|
|
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/102"]}
|
|
)
|
|
assert f"Unknown error while processing {device}" in caplog.text
|
|
capture_exception.assert_called_once_with(err)
|
|
|
|
# We should be able to debug these situations if necessary
|
|
caplog.set_level(logging.DEBUG, "supervisor.dbus.network")
|
|
await network_manager.update()
|
|
with patch.object(DBus, "init_proxy", side_effect=DBusFatalError()):
|
|
await network_manager.update(
|
|
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/103"]}
|
|
)
|
|
assert f"Can't process {device}" in caplog.text
|
|
|
|
await network_manager.update()
|
|
with patch.object(DBus, "properties", new=PropertyMock(return_value=None)):
|
|
await network_manager.update(
|
|
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/104"]}
|
|
)
|
|
assert f"Can't process {device}" in caplog.text
|
|
|
|
|
|
async def test_ignore_veth_only_changes(
|
|
network_manager_service: NetworkManagerService, network_manager: NetworkManager
|
|
):
|
|
"""Changes to list of devices is ignored unless it changes managed devices."""
|
|
assert network_manager.properties["Devices"] == [
|
|
"/org/freedesktop/NetworkManager/Devices/1",
|
|
"/org/freedesktop/NetworkManager/Devices/3",
|
|
"/org/freedesktop/NetworkManager/Devices/38",
|
|
]
|
|
with patch.object(NetworkInterface, "connect") as connect:
|
|
network_manager_service.emit_properties_changed(
|
|
{
|
|
"Devices": [
|
|
"/org/freedesktop/NetworkManager/Devices/1",
|
|
"/org/freedesktop/NetworkManager/Devices/3",
|
|
"/org/freedesktop/NetworkManager/Devices/35",
|
|
"/org/freedesktop/NetworkManager/Devices/38",
|
|
]
|
|
}
|
|
)
|
|
await network_manager_service.ping()
|
|
connect.assert_not_called()
|
|
|
|
network_manager_service.emit_properties_changed(
|
|
{"Devices": ["/org/freedesktop/NetworkManager/Devices/35"]}
|
|
)
|
|
await network_manager_service.ping()
|
|
connect.assert_called_once()
|
|
|
|
|
|
async def test_network_manager_stopped(
|
|
network_manager_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
|
|
network_manager: NetworkManager,
|
|
dbus_session_bus: MessageBus,
|
|
caplog: pytest.LogCaptureFixture,
|
|
capture_exception: Mock,
|
|
):
|
|
"""Test network manager stopped and dbus service no longer accessible."""
|
|
services = list(network_manager_services.values())
|
|
while services:
|
|
service = services.pop(0)
|
|
if isinstance(service, dict):
|
|
services.extend(service.values())
|
|
else:
|
|
dbus_session_bus.unexport(service.object_path, service)
|
|
await dbus_session_bus.release_name("org.freedesktop.NetworkManager")
|
|
|
|
assert network_manager.is_connected is True
|
|
await network_manager.update(
|
|
{
|
|
"Devices": [
|
|
"/org/freedesktop/NetworkManager/Devices/9",
|
|
"/org/freedesktop/NetworkManager/Devices/15",
|
|
"/org/freedesktop/NetworkManager/Devices/20",
|
|
"/org/freedesktop/NetworkManager/Devices/35",
|
|
]
|
|
}
|
|
)
|
|
|
|
capture_exception.assert_called_once()
|
|
assert isinstance(capture_exception.call_args.args[0], DBusServiceUnkownError)
|
|
assert "NetworkManager not responding" in caplog.text
|
|
|
|
|
|
async def test_primary_connection_update(
|
|
network_manager_service: NetworkManagerService,
|
|
network_manager: NetworkManager,
|
|
):
|
|
"""Test handling of primary connection change."""
|
|
interface = next(
|
|
(
|
|
intr
|
|
for intr in network_manager.interfaces
|
|
if intr.object_path == "/org/freedesktop/NetworkManager/Devices/1"
|
|
),
|
|
None,
|
|
)
|
|
await network_manager.update({"PrimaryConnection": "/"})
|
|
assert interface.primary is False
|
|
|
|
await network_manager.update(
|
|
{"PrimaryConnection": "/org/freedesktop/NetworkManager/ActiveConnection/1"}
|
|
)
|
|
assert interface.primary is True
|