1
0
mirror of https://github.com/home-assistant/supervisor.git synced 2026-07-02 19:35:42 +01:00
Files
supervisor/tests/docker/test_monitor.py
Stefan Agner ed91b18c4b tests: enable flake8-pytest-style (PT) ruff rules (#6857)
* 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.
2026-05-20 22:17:54 +02:00

155 lines
4.5 KiB
Python

"""Test docker events monitor."""
import asyncio
from typing import Any
from unittest.mock import patch
from aiodocker.containers import DockerContainer
from awesomeversion import AwesomeVersion
import pytest
from supervisor.bus import Bus
from supervisor.const import BusEvent
from supervisor.coresys import CoreSys
from supervisor.docker.const import ContainerState
from supervisor.docker.monitor import DockerContainerStateEvent
@pytest.mark.parametrize(
("event", "expected", "expected_exit_code"),
[
(
{
"Type": "container",
"Action": "start",
"Actor": {"Attributes": {"supervisor_managed": ""}},
},
ContainerState.RUNNING,
None,
),
(
{
"Type": "container",
"Action": "die",
"Actor": {"Attributes": {"supervisor_managed": "", "exitCode": "0"}},
},
ContainerState.STOPPED,
None,
),
(
{
"Type": "container",
"Action": "die",
"Actor": {"Attributes": {"supervisor_managed": "", "exitCode": "137"}},
},
ContainerState.FAILED,
137,
),
(
{
"Type": "container",
"Action": "health_status: healthy",
"Actor": {"Attributes": {"supervisor_managed": ""}},
},
ContainerState.HEALTHY,
None,
),
(
{
"Type": "container",
"Action": "health_status: unhealthy",
"Actor": {"Attributes": {"supervisor_managed": ""}},
},
ContainerState.UNHEALTHY,
None,
),
(
{
"Type": "container",
"Action": "exec_die",
"Actor": {"Attributes": {"supervisor_managed": ""}},
},
None,
None,
),
(
{
"Type": "container",
"Action": "start",
"Actor": {"Attributes": {}},
},
None,
None,
),
(
{
"Type": "network",
"Action": "start",
"Actor": {"Attributes": {}},
},
None,
None,
),
],
)
async def test_events(
coresys: CoreSys,
event: dict[str, Any],
expected: ContainerState | None,
expected_exit_code: int | None,
):
"""Test events created from docker events."""
event["Actor"]["Attributes"]["name"] = "some_container"
event["Actor"]["ID"] = "abc123"
event["time"] = 123
with patch.object(
Bus, "fire_event", return_value=[coresys.create_task(asyncio.sleep(0))]
) as fire_event:
await coresys.docker.docker.events.channel.publish(event)
await asyncio.sleep(0)
await coresys.docker.monitor.unload()
if expected:
fire_event.assert_called_once_with(
BusEvent.DOCKER_CONTAINER_STATE_CHANGE,
DockerContainerStateEvent(
"some_container", expected, "abc123", 123, expected_exit_code
),
)
else:
fire_event.assert_not_called()
async def test_unlabeled_container(coresys: CoreSys, container: DockerContainer):
"""Test attaching to unlabeled container is still watched."""
container.id = "abc123"
container.show.return_value = {
"Name": "homeassistant",
"Id": "abc123",
"State": {"Status": "running"},
"Config": {},
}
await coresys.homeassistant.core.instance.attach(AwesomeVersion("2022.7.3"))
with patch.object(
Bus, "fire_event", return_value=[coresys.create_task(asyncio.sleep(0))]
) as fire_event:
await coresys.docker.docker.events.channel.publish(
{
"time": 123,
"Type": "container",
"Action": "die",
"Actor": {
"ID": "abc123",
"Attributes": {"name": "homeassistant", "exitCode": "137"},
},
}
)
await coresys.docker.monitor.unload()
fire_event.assert_called_once_with(
BusEvent.DOCKER_CONTAINER_STATE_CHANGE,
DockerContainerStateEvent(
"homeassistant", ContainerState.FAILED, "abc123", 123, 137
),
)