1
0
mirror of https://github.com/home-assistant/supervisor.git synced 2026-05-21 07:08:53 +01:00
Files
supervisor/tests/host/test_control.py
T
Stefan Agner 9923b8580b Return proper API errors for invalid hostnames (#6776)
Follow-up on #6739: with HassioError now logged and captured by Sentry
in api_process, hostname rejections from systemd-hostnamed surfaced as
"unexpected" 400s with noisy log entries and a Sentry event, even
though the user had simply submitted an invalid hostname.

Map this through properly so the API returns a clean, structured 400:

- Split ErrorType.INVALID_ARGS out of DBusInterfaceMethodError into its
  own DBusInvalidArgsError. The two cases collapsed there before are
  semantically different: UNKNOWN_METHOD / INVALID_SIGNATURE mean the
  call is broken (method missing or types wrong); INVALID_ARGS means
  the call is valid but the service rejected an argument's value.
- Add HostInvalidHostnameError(HostError, APIError) with error_key and
  extra_fields so clients get a normalized message and a stable key
  rather than systemd's raw "Invalid static hostname '...'" text.
- Translate DBusInvalidArgsError to HostInvalidHostnameError in
  SystemControl.set_hostname. @api_process turns the result into a 400
  without logging or Sentry capture, since this is now a modeled
  client-input error rather than an unexpected one.

Validation continues to live in hostnamed (hostname_is_valid() in
systemd's src/basic/hostname-util.c); Supervisor only translates the
rejection.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 10:19:27 +02:00

76 lines
2.7 KiB
Python

"""Test host control."""
from dbus_fast import DBusError, ErrorType
import pytest
from supervisor.coresys import CoreSys
from supervisor.exceptions import HostInvalidHostnameError
from tests.dbus_service_mocks.base import DBusServiceMock
from tests.dbus_service_mocks.hostname import Hostname as HostnameService
from tests.dbus_service_mocks.timedate import TimeDate as TimeDateService
async def test_set_hostname(
coresys: CoreSys,
all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
):
"""Test set hostname."""
hostname_service: HostnameService = all_dbus_services["hostname"]
hostname_service.SetStaticHostname.calls.clear()
assert coresys.dbus.hostname.hostname == "homeassistant-n2"
await coresys.host.control.set_hostname("test")
assert hostname_service.SetStaticHostname.calls == [("test", False)]
await hostname_service.ping()
assert coresys.dbus.hostname.hostname == "test"
async def test_set_hostname_rejected_by_host(
coresys: CoreSys,
all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
):
"""A hostname rejected by hostnamed surfaces as HostInvalidHostnameError."""
hostname_service: HostnameService = all_dbus_services["hostname"]
hostname_service.response_set_static_hostname = DBusError(
ErrorType.INVALID_ARGS, "Invalid static hostname 'bad name'"
)
with pytest.raises(HostInvalidHostnameError) as exc_info:
await coresys.host.control.set_hostname("bad name")
assert exc_info.value.error_key == "host_invalid_hostname"
assert exc_info.value.extra_fields == {"hostname": "bad name"}
assert str(exc_info.value) == "Invalid hostname 'bad name'"
@pytest.mark.parametrize("os_available", ["16.2"], indirect=True)
async def test_set_timezone(
coresys: CoreSys,
all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
os_available: str,
):
"""Test set timezone."""
timedate_service: TimeDateService = all_dbus_services["timedate"]
timedate_service.SetTimezone.calls.clear()
assert coresys.dbus.timedate.timezone == "Etc/UTC"
await coresys.host.control.set_timezone("Europe/Prague")
assert timedate_service.SetTimezone.calls == [("Europe/Prague", False)]
@pytest.mark.parametrize("os_available", ["16.1"], indirect=True)
async def test_set_timezone_unsupported(
coresys: CoreSys,
all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
os_available: str,
):
"""Test DBus call is not made when OS doesn't support it."""
timedate_service: TimeDateService = all_dbus_services["timedate"]
timedate_service.SetTimezone.calls.clear()
await coresys.host.control.set_timezone("Europe/Prague")
assert timedate_service.SetTimezone.calls == []