diff --git a/tests/live/test_imap_live.py b/tests/live/test_imap_live.py index f95eef5e..9785c8b4 100644 --- a/tests/live/test_imap_live.py +++ b/tests/live/test_imap_live.py @@ -60,7 +60,7 @@ class TestIMAPLive: pass lines += drain() matched = assert_rfc5424(lines, service="imap", event_type="auth") - assert "failed" in matched, f"Expected auth failure in log. Got:\n{matched!r}" + assert "failure" in matched, f"Expected auth failure in log. Got:\n{matched!r}" def test_select_inbox_after_login(self, live_service): port, drain = live_service("imap") diff --git a/tests/live/test_pop3_live.py b/tests/live/test_pop3_live.py index c9855ad0..9a709fcc 100644 --- a/tests/live/test_pop3_live.py +++ b/tests/live/test_pop3_live.py @@ -55,4 +55,4 @@ class TestPOP3Live: pass lines += drain() matched = assert_rfc5424(lines, service="pop3", event_type="auth") - assert "failed" in matched, f"Expected auth failure in log. Got:\n{matched!r}" + assert "failure" in matched, f"Expected auth failure in log. Got:\n{matched!r}" diff --git a/tests/live/test_service_isolation_live.py b/tests/live/test_service_isolation_live.py index 35cbe87d..07d1be3b 100644 --- a/tests/live/test_service_isolation_live.py +++ b/tests/live/test_service_isolation_live.py @@ -137,29 +137,31 @@ class TestCollectorLiveIsolation: """Real collector behaviour against the actual Docker daemon.""" async def test_collector_finds_no_deckies_without_state(self, tmp_path): - """With no deckies in state, collector's container scan finds nothing. + """With no deckies in state and no DECNET labels, the scan rejects + every container. - We avoid calling the full worker because client.events() blocks - the thread indefinitely — instead we test the scan logic directly - against the real Docker daemon. + is_service_container has two acceptance paths: + 1. label-based (decnet.fleet.service / decnet.topology.service) + 2. name match against decnet-state.json + + With state empty AND labels absent, both paths must reject. We + feed synthetic container objects (no real Docker call) so the + result is independent of whatever fleet may already be running on + the host — which would otherwise satisfy path (1). """ - import docker import decnet.config as cfg + from unittest.mock import MagicMock original_state = cfg.STATE_FILE try: cfg.STATE_FILE = tmp_path / "empty-state.json" - # Real Docker client, real container list — but no state means - # is_service_container rejects everything. - client = docker.from_env() - matched = [c for c in client.containers.list() if is_service_container(c)] - client.close() + unlabeled = MagicMock() + unlabeled.name = "some-random-container" + unlabeled.attrs = {"Config": {"Labels": {}}} + unlabeled.labels = {} - assert matched == [], ( - f"Expected no matching containers without state, got: " - f"{[c.name for c in matched]}" - ) + assert is_service_container(unlabeled) is False finally: cfg.STATE_FILE = original_state diff --git a/tests/prober/test_prober_bus.py b/tests/prober/test_prober_bus.py index f640d70a..f9a17ed4 100644 --- a/tests/prober/test_prober_bus.py +++ b/tests/prober/test_prober_bus.py @@ -114,6 +114,10 @@ def test_tcpfp_phase_invokes_publish_fn_on_success(monkeypatch, tmp_path: Path) "sack_ok": True, "timestamp": True, "options_order": "mss,sack,ts,nop,wscale", + "tos": 0, + "dscp": 0, + "ecn": 0, + "server_isn": 0, }, ) diff --git a/tests/prober/test_prober_worker.py b/tests/prober/test_prober_worker.py index 231ae83b..76a8ef57 100644 --- a/tests/prober/test_prober_worker.py +++ b/tests/prober/test_prober_worker.py @@ -342,6 +342,7 @@ class TestProbeCycleTCPFP: "ttl": 64, "window_size": 65535, "df_bit": 1, "mss": 1460, "window_scale": 7, "sack_ok": 1, "timestamp": 1, "options_order": "M,N,W,N,N,T,S,E", + "tos": 0, "dscp": 0, "ecn": 0, "server_isn": 0, } log_path = tmp_path / "decnet.log" json_path = tmp_path / "decnet.json" @@ -368,6 +369,7 @@ class TestProbeCycleTCPFP: "ttl": 128, "window_size": 8192, "df_bit": 1, "mss": 1460, "window_scale": 8, "sack_ok": 1, "timestamp": 0, "options_order": "M,N,W,N,N,S", + "tos": 0, "dscp": 0, "ecn": 0, "server_isn": 0, } log_path = tmp_path / "decnet.log" json_path = tmp_path / "decnet.json" diff --git a/tests/services/test_service_isolation.py b/tests/services/test_service_isolation.py index d82f125b..001cf4a8 100644 --- a/tests/services/test_service_isolation.py +++ b/tests/services/test_service_isolation.py @@ -16,6 +16,7 @@ worker degrades gracefully while unrelated workers remain unaffected. """ import asyncio +import contextlib import json import os import time @@ -68,8 +69,12 @@ class TestCollectorIsolation: with patch("decnet.config.load_state", return_value=None): task = asyncio.create_task(log_collector_worker("/tmp/decnet-test-collector.log")) await asyncio.sleep(0.1) - assert task.done() - assert task.exception() is None + # Collector now retries on event-stream errors instead of + # exiting; it should still be running (i.e. surviving) here. + assert not task.done() + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task def test_collector_container_filter_with_unknown_containers(self): """is_service_container must reject containers not in state.""" diff --git a/tests/swarm/test_heartbeat.py b/tests/swarm/test_heartbeat.py index 445987ac..6bd3f973 100644 --- a/tests/swarm/test_heartbeat.py +++ b/tests/swarm/test_heartbeat.py @@ -167,7 +167,7 @@ def test_heartbeat_happy_path_primary_extraction( assert s["archetype"] == "generic" assert s["service_config"] == {"ssh": {"port": 22}} - asyncio.get_event_loop().run_until_complete(_verify()) + asyncio.run(_verify()) def test_heartbeat_fallback_extraction_path_also_accepted( @@ -241,7 +241,7 @@ def test_heartbeat_decommissioned_host_returns_404( ok = await repo.delete_swarm_host(host["host_uuid"]) assert ok - asyncio.get_event_loop().run_until_complete(_delete()) + asyncio.run(_delete()) _pin_fingerprint(monkeypatch, fp) resp = client.post( @@ -272,7 +272,7 @@ def test_heartbeat_deployed_false_bumps_host_but_writes_no_shards( shards = await repo.list_decky_shards(host["host_uuid"]) assert shards == [] - asyncio.get_event_loop().run_until_complete(_verify()) + asyncio.run(_verify()) def test_heartbeat_decky_missing_from_runtime_is_degraded( @@ -297,4 +297,4 @@ def test_heartbeat_decky_missing_from_runtime_is_degraded( assert by["decky-01"]["state"] == "running" assert by["decky-02"]["state"] == "degraded" - asyncio.get_event_loop().run_until_complete(_verify()) + asyncio.run(_verify()) diff --git a/tests/swarm/test_swarm_api.py b/tests/swarm/test_swarm_api.py index e43dd595..2ef36899 100644 --- a/tests/swarm/test_swarm_api.py +++ b/tests/swarm/test_swarm_api.py @@ -464,7 +464,7 @@ def test_list_deckies_joins_host_identity(client: TestClient, repo) -> None: "services": ["smb", "ssh"], "state": "failed", "last_error": "boom", }) - asyncio.get_event_loop().run_until_complete(_seed()) + asyncio.run(_seed()) rows = client.get("/swarm/deckies").json() assert len(rows) == 2