From 52f2f65fa36f9d895c9e46e7dd72a4e177c85395 Mon Sep 17 00:00:00 2001 From: anti Date: Sun, 10 May 2026 22:10:54 -0400 Subject: [PATCH] fix(tests): fix stale asyncio.sleep patches and missing tarpit guards in service isolation tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the ingester._sleep alias fix, three tests in test_service_isolation.py still patched `decnet.web.ingester.asyncio.sleep` (the old global-singleton path). The ingester now calls `_sleep` directly, so those patches no longer controlled the ingester's sleep — the worker looped with real asyncio.sleep and the tests hung indefinitely. Also: four API lifespan tests had no tarpit_watcher_worker patch, letting the real tarpit task start. And test_api_survives_db_init_failure patched `decnet.web.api.asyncio.sleep` (the singleton) instead of the existing `_retry_sleep` alias. Fixes: - patch("decnet.web.ingester._sleep", ...) in the three ingester tests - add tarpit_watcher_worker patch to all four api lifespan tests - patch("decnet.web.api._retry_sleep", ...) in db_init_failure test --- tests/services/test_service_isolation.py | 59 +++++++++++++----------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/tests/services/test_service_isolation.py b/tests/services/test_service_isolation.py index 001cf4a8..04d6d665 100644 --- a/tests/services/test_service_isolation.py +++ b/tests/services/test_service_isolation.py @@ -110,7 +110,7 @@ class TestIngesterIsolation: raise asyncio.CancelledError() with patch.dict(os.environ, {"DECNET_INGEST_LOG_FILE": "/tmp/nonexistent-decnet-test.log"}): - with patch("decnet.web.ingester.asyncio.sleep", side_effect=_controlled_sleep): + with patch("decnet.web.ingester._sleep", side_effect=_controlled_sleep): task = asyncio.create_task(log_ingestion_worker(mock_repo)) with pytest.raises(asyncio.CancelledError): await task @@ -153,7 +153,7 @@ class TestIngesterIsolation: raise asyncio.CancelledError() with patch.dict(os.environ, {"DECNET_INGEST_LOG_FILE": str(tmp_path / "test.log")}): - with patch("decnet.web.ingester.asyncio.sleep", side_effect=_controlled_sleep): + with patch("decnet.web.ingester._sleep", side_effect=_controlled_sleep): task = asyncio.create_task(log_ingestion_worker(mock_repo)) with pytest.raises(asyncio.CancelledError): await task @@ -345,9 +345,11 @@ class TestApiLifespanIsolation: side_effect=Exception("attacker exploded")): with patch("decnet.sniffer.sniffer_worker", side_effect=Exception("sniffer exploded")): - # API should still start - async with lifespan(mock_app): - mock_repo.initialize.assert_awaited_once() + with patch("decnet.web.api.tarpit_watcher_worker", + return_value=asyncio.sleep(0)): + # API should still start + async with lifespan(mock_app): + mock_repo.initialize.assert_awaited_once() @pytest.mark.asyncio async def test_api_survives_db_init_failure(self): @@ -359,13 +361,15 @@ class TestApiLifespanIsolation: mock_repo.initialize = AsyncMock(side_effect=Exception("DB locked")) with patch("decnet.web.api.repo", mock_repo): - with patch("decnet.web.api.asyncio.sleep", new_callable=AsyncMock): + with patch("decnet.web.api._retry_sleep", new_callable=AsyncMock): with patch("decnet.web.api.log_ingestion_worker", return_value=asyncio.sleep(0)): with patch("decnet.web.api.log_collector_worker", return_value=asyncio.sleep(0)): with patch("decnet.web.api.attacker_profile_worker", return_value=asyncio.sleep(0)): - async with lifespan(mock_app): - # DB init failed 5 times but API is running - assert mock_repo.initialize.await_count == 5 + with patch("decnet.web.api.tarpit_watcher_worker", + return_value=asyncio.sleep(0)): + async with lifespan(mock_app): + # DB init failed 5 times but API is running + assert mock_repo.initialize.await_count == 5 @pytest.mark.asyncio async def test_api_survives_sniffer_import_failure(self): @@ -376,22 +380,23 @@ class TestApiLifespanIsolation: mock_repo = MagicMock() mock_repo.initialize = AsyncMock() + import builtins + real_import = builtins.__import__ + + def _mock_import(name, *args, **kwargs): + if name == "decnet.sniffer": + raise ImportError("No module named 'scapy'") + return real_import(name, *args, **kwargs) + with patch("decnet.web.api.repo", mock_repo): with patch("decnet.web.api.log_ingestion_worker", return_value=asyncio.sleep(0)): with patch("decnet.web.api.log_collector_worker", return_value=asyncio.sleep(0)): with patch("decnet.web.api.attacker_profile_worker", return_value=asyncio.sleep(0)): - # Simulate sniffer import failure - import builtins - real_import = builtins.__import__ - - def _mock_import(name, *args, **kwargs): - if name == "decnet.sniffer": - raise ImportError("No module named 'scapy'") - return real_import(name, *args, **kwargs) - - with patch("builtins.__import__", side_effect=_mock_import): - async with lifespan(mock_app): - mock_repo.initialize.assert_awaited_once() + with patch("decnet.web.api.tarpit_watcher_worker", + return_value=asyncio.sleep(0)): + with patch("builtins.__import__", side_effect=_mock_import): + async with lifespan(mock_app): + mock_repo.initialize.assert_awaited_once() @pytest.mark.asyncio async def test_shutdown_handles_already_dead_tasks(self): @@ -410,10 +415,12 @@ class TestApiLifespanIsolation: with patch("decnet.web.api.log_ingestion_worker", side_effect=_instant_worker): with patch("decnet.web.api.log_collector_worker", side_effect=_instant_worker): with patch("decnet.web.api.attacker_profile_worker", side_effect=_instant_worker): - async with lifespan(mock_app): - # Let tasks finish - await asyncio.sleep(0.05) - # Shutdown should handle already-done tasks gracefully + with patch("decnet.web.api.tarpit_watcher_worker", + side_effect=_instant_worker): + async with lifespan(mock_app): + # Let tasks finish + await asyncio.sleep(0.05) + # Shutdown should handle already-done tasks gracefully # ─── Cross-service cascade tests ──────────────────────────────────────────── @@ -442,7 +449,7 @@ class TestCascadeIsolation: raise asyncio.CancelledError() with patch.dict(os.environ, {"DECNET_INGEST_LOG_FILE": str(tmp_path / "cascade.log")}): - with patch("decnet.web.ingester.asyncio.sleep", side_effect=_controlled_sleep): + with patch("decnet.web.ingester._sleep", side_effect=_controlled_sleep): task = asyncio.create_task(log_ingestion_worker(mock_repo)) with pytest.raises(asyncio.CancelledError): await task