mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-07-02 19:35:42 +01:00
ed91b18c4b
* tests: enable flake8-pytest-style (PT) ruff rules Enable the `PT` ruff rule set and fix the resulting violations across the test suite: - PT006: pass parametrize argument names as tuples instead of a single comma-separated string. - PT022: switch fixtures that have no teardown from `yield` to `return` so the lack of cleanup is obvious at a glance. - PT011: add `match=` to broad `pytest.raises(ValueError)` blocks so the expected error is anchored to a specific message. - PT012: hoist setup (patches, branching) out of `pytest.raises()` blocks so only the call that is expected to raise remains inside. - PT013: replace `from pytest import X` with `import pytest` and access attributes via the module. - PT015: replace `try/except` + `assert False` patterns with `pytest.raises(...)`. - PT017: replace `assert` on exceptions inside `except` blocks with `pytest.raises(...) as exc_info` and assert on `exc_info.value`. No behavioral changes to the tests; the full suite still passes. * tests: address review feedback on PT ruff rule enablement - Fix fixture return-type annotations after switching `yield` to `return` in tests/conftest.py: drop the `Generator[...]`/`AsyncGenerator[...]` wrapper for `dns_manager_service`, `supervisor_internet`, `websession`, and `mock_update_data` so the annotation matches what the fixture actually returns. - Correct the return-type annotation of `fixture_ip6config_service` from `IP4ConfigService` to `IP6ConfigService`. - Fix recurring "excepiton" typo in tests/utils/test_exception_helper.py. * tests: verify backup cleanup on permission error After `test_new_backup_permission_error` raises `BackupPermissionError`, assert that no tarfile was left behind and `tmp_path` is empty. The previous version only checked that the exception was raised, which missed any regression where a partial tarfile would survive the failed create. * tests: rename DNS_GOOD_V6 to DNS_V6_UNSUPPORTED The constant was named "good" but its tests assert that the URLs are rejected by the DNS validator. The IPv6 URLs are well-formed but currently rejected because IPv6 doesn't work with the Docker network (see `dns_url` in supervisor/validate.py). Rename the constant and the related test to make the intent obvious.
325 lines
9.2 KiB
Python
325 lines
9.2 KiB
Python
"""Tests for D-Bus tolerant enum base classes."""
|
|
|
|
import logging
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from supervisor.dbus.const import (
|
|
ConnectionState,
|
|
ConnectionType,
|
|
ConnectivityState,
|
|
DeviceType,
|
|
DNSOverTLSEnabled,
|
|
InterfaceMethod,
|
|
MulticastProtocolEnabled,
|
|
RaucState,
|
|
UnitActiveState,
|
|
WirelessMethodType,
|
|
)
|
|
from supervisor.dbus.enum import DBusIntEnum, DBusStrEnum, _reported
|
|
from supervisor.dbus.udisks2.const import PartitionTableType
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_reported():
|
|
"""Clear the deduplication set between tests."""
|
|
_reported.clear()
|
|
|
|
|
|
# -- Test fixtures: concrete subclasses for isolated testing --
|
|
|
|
|
|
class SampleStrEnum(DBusStrEnum):
|
|
"""Sample StrEnum for testing."""
|
|
|
|
ALPHA = "alpha"
|
|
BETA = "beta"
|
|
|
|
|
|
class SampleIntEnum(DBusIntEnum):
|
|
"""Sample IntEnum for testing."""
|
|
|
|
ONE = 1
|
|
TWO = 2
|
|
|
|
|
|
# -- DBusStrEnum tests --
|
|
|
|
|
|
def test_str_known_value():
|
|
"""Test known value returns the defined member."""
|
|
assert SampleStrEnum("alpha") is SampleStrEnum.ALPHA
|
|
assert SampleStrEnum("beta") is SampleStrEnum.BETA
|
|
|
|
|
|
def test_str_unknown_value_returns_pseudo_member(caplog):
|
|
"""Test unknown value creates a pseudo-member."""
|
|
with caplog.at_level(logging.WARNING):
|
|
result = SampleStrEnum("gamma")
|
|
|
|
assert isinstance(result, SampleStrEnum)
|
|
assert result.value == "gamma"
|
|
assert result.name == "gamma"
|
|
assert "Unknown SampleStrEnum value received from D-Bus: gamma" in caplog.text
|
|
|
|
|
|
def test_str_unknown_value_str():
|
|
"""Test unknown value behaves as str."""
|
|
result = SampleStrEnum("gamma")
|
|
assert str(result) == "gamma"
|
|
assert result == "gamma"
|
|
|
|
|
|
def test_str_members_not_polluted():
|
|
"""Test pseudo-members don't appear in __members__ or list()."""
|
|
SampleStrEnum("gamma")
|
|
assert "gamma" not in SampleStrEnum.__members__
|
|
assert set(SampleStrEnum) == {SampleStrEnum.ALPHA, SampleStrEnum.BETA}
|
|
|
|
|
|
def test_str_non_str_raises_value_error():
|
|
"""Test non-string values raise ValueError."""
|
|
with pytest.raises(ValueError, match="is not a valid SampleStrEnum"):
|
|
SampleStrEnum(123)
|
|
|
|
|
|
def test_str_hash_consistency():
|
|
"""Test pseudo-members hash like their string value."""
|
|
result = SampleStrEnum("gamma")
|
|
assert hash(result) == hash("gamma")
|
|
assert {result: True}["gamma"]
|
|
|
|
|
|
def test_str_match_known():
|
|
"""Test match statement with known value."""
|
|
val = SampleStrEnum("alpha")
|
|
match val:
|
|
case SampleStrEnum.ALPHA:
|
|
matched = "alpha"
|
|
case _:
|
|
matched = "default"
|
|
assert matched == "alpha"
|
|
|
|
|
|
def test_str_match_unknown_falls_to_default():
|
|
"""Test match statement with unknown value falls to default."""
|
|
val = SampleStrEnum("gamma")
|
|
match val:
|
|
case SampleStrEnum.ALPHA:
|
|
matched = "alpha"
|
|
case SampleStrEnum.BETA:
|
|
matched = "beta"
|
|
case _:
|
|
matched = "default"
|
|
assert matched == "default"
|
|
|
|
|
|
# -- DBusIntEnum tests --
|
|
|
|
|
|
def test_int_known_value():
|
|
"""Test known value returns the defined member."""
|
|
assert SampleIntEnum(1) is SampleIntEnum.ONE
|
|
assert SampleIntEnum(2) is SampleIntEnum.TWO
|
|
|
|
|
|
def test_int_unknown_value_returns_pseudo_member(caplog):
|
|
"""Test unknown value creates a pseudo-member."""
|
|
with caplog.at_level(logging.WARNING):
|
|
result = SampleIntEnum(999)
|
|
|
|
assert isinstance(result, SampleIntEnum)
|
|
assert result.value == 999
|
|
assert result.name == "UNKNOWN_999"
|
|
assert "Unknown SampleIntEnum value received from D-Bus: 999" in caplog.text
|
|
|
|
|
|
def test_int_unknown_value_int():
|
|
"""Test unknown value behaves as int."""
|
|
result = SampleIntEnum(999)
|
|
assert int(result) == 999
|
|
assert result == 999
|
|
|
|
|
|
def test_int_members_not_polluted():
|
|
"""Test pseudo-members don't appear in __members__ or list()."""
|
|
SampleIntEnum(999)
|
|
assert "UNKNOWN_999" not in SampleIntEnum.__members__
|
|
assert set(SampleIntEnum) == {SampleIntEnum.ONE, SampleIntEnum.TWO}
|
|
|
|
|
|
def test_int_non_int_raises_value_error():
|
|
"""Test non-integer values raise ValueError."""
|
|
with pytest.raises(ValueError, match="is not a valid SampleIntEnum"):
|
|
SampleIntEnum("abc")
|
|
|
|
|
|
def test_int_hash_consistency():
|
|
"""Test pseudo-members hash like their int value."""
|
|
result = SampleIntEnum(999)
|
|
assert hash(result) == hash(999)
|
|
assert {result: True}[999]
|
|
|
|
|
|
def test_int_match_known():
|
|
"""Test match statement with known value."""
|
|
val = SampleIntEnum(1)
|
|
match val:
|
|
case SampleIntEnum.ONE:
|
|
matched = "one"
|
|
case _:
|
|
matched = "default"
|
|
assert matched == "one"
|
|
|
|
|
|
def test_int_match_unknown_falls_to_default():
|
|
"""Test match statement with unknown value falls to default."""
|
|
val = SampleIntEnum(999)
|
|
match val:
|
|
case SampleIntEnum.ONE:
|
|
matched = "one"
|
|
case SampleIntEnum.TWO:
|
|
matched = "two"
|
|
case _:
|
|
matched = "default"
|
|
assert matched == "default"
|
|
|
|
|
|
# -- Integration tests with actual D-Bus enums --
|
|
|
|
|
|
def test_device_type_unknown():
|
|
"""Test DeviceType handles unknown device types."""
|
|
result = DeviceType(999)
|
|
assert isinstance(result, DeviceType)
|
|
assert result.value == 999
|
|
assert result != DeviceType.UNKNOWN
|
|
|
|
|
|
def test_device_type_known():
|
|
"""Test DeviceType still works for known values."""
|
|
assert DeviceType(1) is DeviceType.ETHERNET
|
|
assert DeviceType(2) is DeviceType.WIRELESS
|
|
|
|
|
|
def test_unit_active_state_unknown():
|
|
"""Test UnitActiveState handles unknown states."""
|
|
result = UnitActiveState("refreshing")
|
|
assert isinstance(result, UnitActiveState)
|
|
assert result.value == "refreshing"
|
|
|
|
|
|
def test_unit_active_state_known():
|
|
"""Test UnitActiveState still works for known values."""
|
|
assert UnitActiveState("active") is UnitActiveState.ACTIVE
|
|
|
|
|
|
def test_rauc_state_unknown():
|
|
"""Test RaucState handles unknown states."""
|
|
result = RaucState("testing")
|
|
assert isinstance(result, RaucState)
|
|
assert result.value == "testing"
|
|
|
|
|
|
def test_connection_type_unknown():
|
|
"""Test ConnectionType handles unknown types."""
|
|
result = ConnectionType("802-11-olpc-mesh")
|
|
assert isinstance(result, ConnectionType)
|
|
assert result.value == "802-11-olpc-mesh"
|
|
|
|
|
|
def test_connection_state_unknown():
|
|
"""Test ConnectionState handles unknown states."""
|
|
result = ConnectionState(99)
|
|
assert isinstance(result, ConnectionState)
|
|
assert result.value == 99
|
|
|
|
|
|
def test_connectivity_state_unknown():
|
|
"""Test ConnectivityState handles unknown states."""
|
|
result = ConnectivityState(99)
|
|
assert isinstance(result, ConnectivityState)
|
|
assert result.value == 99
|
|
|
|
|
|
def test_wireless_method_type_unknown():
|
|
"""Test WirelessMethodType handles unknown types."""
|
|
result = WirelessMethodType(99)
|
|
assert isinstance(result, WirelessMethodType)
|
|
assert result.value == 99
|
|
|
|
|
|
def test_interface_method_unknown():
|
|
"""Test InterfaceMethod handles unknown methods."""
|
|
result = InterfaceMethod("shared")
|
|
assert isinstance(result, InterfaceMethod)
|
|
assert result.value == "shared"
|
|
|
|
|
|
def test_multicast_protocol_enabled_unknown():
|
|
"""Test MulticastProtocolEnabled handles unknown values."""
|
|
result = MulticastProtocolEnabled("maybe")
|
|
assert isinstance(result, MulticastProtocolEnabled)
|
|
assert result.value == "maybe"
|
|
|
|
|
|
def test_dns_over_tls_enabled_unknown():
|
|
"""Test DNSOverTLSEnabled handles unknown values."""
|
|
result = DNSOverTLSEnabled("strict")
|
|
assert isinstance(result, DNSOverTLSEnabled)
|
|
assert result.value == "strict"
|
|
|
|
|
|
def test_partition_table_type_unknown():
|
|
"""Test PartitionTableType handles unknown types."""
|
|
result = PartitionTableType("mbr")
|
|
assert isinstance(result, PartitionTableType)
|
|
assert result.value == "mbr"
|
|
|
|
|
|
# -- Sentry reporting tests --
|
|
|
|
|
|
@patch("supervisor.dbus.enum.fire_and_forget_capture_message")
|
|
def test_unknown_str_reports_to_sentry(mock_capture):
|
|
"""Test unknown StrEnum value is reported to Sentry."""
|
|
SampleStrEnum("delta")
|
|
mock_capture.assert_called_once_with(
|
|
"Unknown SampleStrEnum value received from D-Bus: delta"
|
|
)
|
|
|
|
|
|
@patch("supervisor.dbus.enum.fire_and_forget_capture_message")
|
|
def test_unknown_int_reports_to_sentry(mock_capture):
|
|
"""Test unknown IntEnum value is reported to Sentry."""
|
|
SampleIntEnum(777)
|
|
mock_capture.assert_called_once_with(
|
|
"Unknown SampleIntEnum value received from D-Bus: 777"
|
|
)
|
|
|
|
|
|
@patch("supervisor.dbus.enum.fire_and_forget_capture_message")
|
|
def test_duplicate_not_reported_twice(mock_capture):
|
|
"""Test the same unknown value is only reported to Sentry once."""
|
|
SampleIntEnum(888)
|
|
SampleIntEnum(888)
|
|
SampleIntEnum(888)
|
|
mock_capture.assert_called_once()
|
|
|
|
|
|
@patch("supervisor.dbus.enum.fire_and_forget_capture_message")
|
|
def test_different_values_each_reported(mock_capture):
|
|
"""Test different unknown values are each reported separately."""
|
|
SampleIntEnum(100)
|
|
SampleIntEnum(200)
|
|
assert mock_capture.call_count == 2
|
|
|
|
|
|
@patch("supervisor.dbus.enum.fire_and_forget_capture_message")
|
|
def test_known_value_not_reported(mock_capture):
|
|
"""Test known values don't trigger Sentry reports."""
|
|
SampleStrEnum("alpha")
|
|
SampleIntEnum(1)
|
|
mock_capture.assert_not_called()
|